在go語言中,數組是一個由固定長度的特定類型元素組成的序列,是同一種資料類型元素的集合,一個數組可以由零個或多個元素組成。和數組對應的類型是Slice(切片),切片是對數組的一個連續片段的引用,所以切片是一個引用類型,這個片段可以是整個數組,也可以是由起始和終止索引標識的一些項的子集,需要注意的是,終止索引標識的項目不包括在切片內。
本教學操作環境:windows7系統、GO 1.18版本、Dell G3電腦。
陣列是同一種資料型態元素的集合
。在Go語言中,陣列從宣告時就決定
,使用時可以修改陣列成員,但是陣列大小不可變更
。基本語法:
// 定义一个长度为3元素类型为int的数组a var a [3]int
陣列的長度必須是常數,並且長度是陣列類型的一部分。一旦定義,長度不能變
var testArray [3]int // 定义数组时,会初始化int类型为零值 var cityArray = [3]string{"北京", "上海", "深圳"} // 使用指定的初始值完成初始化
在一般情況下我們可以讓編譯器根據初始值的個數自行推斷數組的長度
var cityArray = [...]string{"北京", "上海", "深圳"}
我們也可以使用指定索引值的方式來初始化數組,例如:
func main() { a := [...]int{1: 1, 3: 5} fmt.Println(a) // [0 1 0 5] fmt.Printf("type of a:%T\n", a) //type of a:[4]int }
func main() { var a = [...]string{"北京", "上海", "深圳"} // 方法1:for循环遍历 for i := 0; i < len(a); i++ { fmt.Println(a[i]) } // 方法2:for range遍历 for index, value := range a { fmt.Println(index, value) } }
Go語言是支援多維數組的,我們這裡以二維數組為例(數組中又嵌套數組)。
func main() { a := [3][2]string{ {"北京", "上海"}, {"广州", "深圳"}, {"成都", "重庆"}, } fmt.Println(a) //[[北京 上海] [广州 深圳] [成都 重庆]] fmt.Println(a[2][1]) //支持索引取值:重庆 }
func main() { a := [3][2]string{ {"北京", "上海"}, {"广州", "深圳"}, {"成都", "重庆"}, } for _, v1 := range a { for _, v2 := range v1 { fmt.Printf("%s\t", v2) } fmt.Println() } }
##注意:只有第一層
可以使用...
來讓編譯器推導出陣列長度。例如:a := [...][2]string{ {"北京", "上海"}, {"广州", "深圳"}, {"成都", "重庆"}, }
改變副本的值,不會改變本身的值。
func modifyArray(x [3]int) { x[0] = 100 } func modifyArray2(x [3][2]int) { x[2][0] = 100 } func main() { a := [3]int{10, 20, 30} modifyArray(a) //在modify中修改的是a的副本x fmt.Println(a) //[10 20 30] b := [3][2]int{ {1, 1}, {1, 1}, {1, 1}, } modifyArray2(b) //在modify中修改的是b的副本x fmt.Println(b) //[[1 1] [1 1] [1 1]] }
注意:
陣列支援「==「、」!=」運算符,因為記憶體總是被初始化過的。 [n]*T
表示指標陣列(這是一個陣列
,裡面元素是一個個的指標)##* [n]T
指標
,存的是一個陣列的記憶體位址)
切片(Slice)是一個擁有相同類型元素的
可變長度的序列。它是
基於陣列類型做的一層封裝引用類型
。切片一般用於快速地操作一塊資料集合。 切片(slice)是對數組的一個連續片段的引用,所以切片是一個引用類型(因此更類似於C/C 中的數組類型,或者 Python 中的list 類型),這個片段可以是整個數組,也可以是起始和終止索引標識的一些項目的子集,需要注意的是,終止索引標識的項不包括在切片內。
Go語言中切片的內部結構包含地址、大小和容量,切片一般用於快速地操作一塊數據集合,如果將數據集合比作切糕的話,切片就是你要的“那一塊” ,切的過程包含從何處開始(切片的起始位置)及切多大(切片的大小),容量可以理解為裝切片的口袋大小。
1、切片的定義
聲明切片類型的基本語法如下:
var name []T // name:表示变量名 // T:表示切片中的元素类型2、切片的長度和容量##########切片擁有自己的長度和容量,我們可以透過使用內建的len()函數來求長度,使用內建的cap()函數求切片的容量。 ###############3、切片表達式#########切片表達式###從字串、陣列、指向陣列或切片的指標建構子字符串或切片###。它有兩種變體:一種###指定low和high兩個索引界限值的簡單的形式###,另一種是除了low和high索引界限值外###還指定容量的完整的形式###。 #########完整切片表達式沒啥用,這裡只講簡單切片表達式! ######
// 简单切片表达式 func main() { a := [5]int{1, 2, 3, 4, 5} s := a[1:3] // s := a[low:high] fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s)) }###運行結果:###
s:[2 3] len(s):2 cap(s):4#########(1)使用make()函數建構切片######我們上面都是基於陣列來創建的切片,如果需要動態的建立一個切片,我們就需要使用內建的make()函數,格式如下:###
make([]T, size, cap)
举个栗子:
func main() { a := make([]int, 2, 10) fmt.Println(a) //[0 0] fmt.Println(len(a)) //2 fmt.Println(cap(a)) //10 }
上面代码中a
的内部存储空间已经分配了10个,但实际上只用了2个。 容量并不会影响当前元素的个数,所以len(a)
返回2,cap(a)
则返回该切片的容量。
切片自己不拥有任何数据。它只是底层数组的一种表示。对切片所做的任何修改都会反映在底层数组中
。
切片的本质 就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)
。
举个例子,现在有一个数组a := [8]int{0, 1, 2, 3, 4, 5, 6, 7},切片s1 := a[:5],相应示意图如下。
切片s2 := a[3:6],相应示意图如下:
如果你懂了切片的本质,那么试试下面这个题吧!
func main() { a := [5]int{1, 2, 3, 4, 5} s := a[1:3] // s := a[low:high] fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s)) s2 := s[3:4] // 索引的上限是cap(s)而不是len(s),可能认为cap是2?切片是从原数组中元素2开始切走的 fmt.Printf("s2:%v len(s2):%v cap(s2):%v\n", s2, len(s2), cap(s2)) }
运行结果:
s:[2 3] len(s):2 cap(s):4 s2:[5] len(s2):1 cap(s2):1
s2什么鬼?[2 3][3:4]
这个能运行?如果有这样的疑惑,说明你并没有认识到切片的本质,下面我们来看一个图:
注意切片的本质是一个指向底层数组的起点的指针
,切片len有效长度
,以及cap容量
。
上面是切片s生成的过程,现在又要切片取[3:4],从s的起点开始数
,我们可以很容易看出来[3:4]是5。
切片之间是不能比较的
,我们不能使用==操作符来判断两个切片是否含有全部相等元素。 切片唯一合法的比较操作是和nil比较
。 一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0
。但是我们不能说一个长度和容量都是0的切片一定是nil
,例如下面的示例:
var s1 []int //len(s1)=0;cap(s1)=0;s1==nil s2 := []int{} //len(s2)=0;cap(s2)=0;s2!=nil s3 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil
所以要判断一个切片是否是空的,要是用len(s) == 0
来判断,不应该使用s == nil
来判断。
注意:nil和空不是一个概念,nil的判断是有无底层数组,s2、s3初始化了的,其实是有底层数组的,s1只是声明,因此没有底层数组为nil。是否为空,则len是否为0为唯一判断条件。
下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容
,这点需要特别注意。
func main() { s1 := make([]int, 3) //[0 0 0] s2 := s1 //将s1直接赋值给s2,s1和s2共用一个底层数组 s2[0] = 100 fmt.Println(s1) //[100 0 0] fmt.Println(s2) //[100 0 0] }
切片的遍历方式和数组是一致的,支持索引遍历
和for range
遍历。
func main() { s := []int{1, 3, 5} for i := 0; i < len(s); i++ { fmt.Println(i, s[i]) } for index, value := range s { fmt.Println(index, value) } }
Go语言的内建函数append()可以为切片动态添加元素。 可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加…)。
func main(){ var s []int s = append(s, 1) // [1] s = append(s, 2, 3, 4) // [1 2 3 4] s2 := []int{5, 6, 7} s = append(s, s2...) // [1 2 3 4 5 6 7] } // 这个...类似于python中的*args打散列表
注意: 通过var声明的零值切片可以在append()函数直接使用,无需初始化。
var s []int s = append(s, 1, 2, 3)
没有必要像下面的代码一样初始化一个切片再传入append()函数使用
s := []int{} // 没有必要初始化 s = append(s, 1, 2, 3) var s = make([]int) // 没有必要初始化 s = append(s, 1, 2, 3)
每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”
,此时该切片指向的底层数组就会更换
。“扩容”操作往往发生在append()函数调用时,所以
我们通常都需要用原变量接收append函数的返回值
。
可以通过查看$GOROOT/src/runtime/slice.go
源码,其中扩容相关代码如下:
newcap := old.cap doublecap := newcap + newcap if cap > doublecap { newcap = cap } else { if old.len < 1024 { newcap = doublecap } else { // Check 0 < newcap to detect overflow // and prevent an infinite loop. for 0 < newcap && newcap < cap { newcap += newcap / 4 } // Set newcap to the requested cap when // the newcap calculation overflowed. if newcap <= 0 { newcap = cap } } }
func main() { a := []int{1, 2, 3, 4, 5} b := a fmt.Println(a) //[1 2 3 4 5] fmt.Println(b) //[1 2 3 4 5] b[0] = 1000 fmt.Println(a) //[1000 2 3 4 5] fmt.Println(b) //[1000 2 3 4 5] }
由于切片是引用类型
,所以a和b其实都指向了同一块内存地址。修改b的同时a的值也会发生变化
。
Go语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中
,copy()函数的使用方法如下:
func main() { // copy()复制切片 a := []int{1, 2, 3, 4, 5} c := make([]int, 5, 5) copy(c, a) //使用copy()函数将切片a中的元素复制到切片c fmt.Println(a) //[1 2 3 4 5] fmt.Println(c) //[1 2 3 4 5] c[0] = 1000 fmt.Println(a) //[1 2 3 4 5] fmt.Println(c) //[1000 2 3 4 5] // 再对切片c操作,就不会影响a了 }
Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。 代码如下:
func main() { // 从切片中删除元素 a := []int{30, 31, 32, 33, 34, 35, 36, 37} // 要删除索引为2的元素 a = append(a[:2], a[3:]...) // 把index=2之后的切片和index=2之前的切片拼接在一起 fmt.Println(a) //[30 31 33 34 35 36 37] }
切片a中删除索引为index
的元素,操作方法是a = append(a[:index], a[index+1:]...)
切片持有对底层数组的引用。只要切片在内存中,数组就不能被垃圾回收
。在内存管理方面,这是需要注意的。让我们假设我们有一个非常大的数组,我们只想处理它的一小部分
。然后,我们由这个数组创建一个切片,并开始处理切片。这里需要重点注意的是,在切片引用时数组仍然存在内存中。
一种解决方法是使用上面的copy函数,根据切片生成一个一模一样的新切片
。这样我们可以使用新的切片,原始数组可以被垃圾回收。
package mainimport ( "fmt")func countries() []string { a := []string{1, 2, 3, 4, 5} b := a[:len(a)-2] c := make([]string, len(b)) copy(c, b) // 将b的内容copy给c return c}func main() { d := countries() fmt.Println(d) }
b := a[:len(a)-2]
创建一个去掉a的尾部 2 个元素的切片 b,在上述程序的 11 行,将 切片b 复制到 切片c。同时在函数的下一行返回 切片c。现在 a 数组可以被垃圾回收, 因为数组a不再被引用。
Go 数组与像 C/C++等语言中数组略有不同:
1. Go 中的数组是值类型,换句话说,如果你将一个数组赋值给另外一个数组,那么,实际上就是将整个数组拷贝一份。因此,在 Go 中如果将数组作为函数的参数传递的话,那效率就肯定没有传递指针高了。
2. 数组的长度也是类型的一部分,这就说明[10]int和[20]int不是同一种数据类型。并且Go 语言中数组的长度是固定的,且不同长度的数组是不同类型,这样的限制带来不少局限性。
3. 而切片则不同,切片(slice)是一个拥有相同类型元素的可变长序列,可以方便地进行扩容和传递,实际使用时比数组更加灵活,这也正是切片存在的意义。而且切片是引用类型,因此在当传递切片时将引用同一指针,修改值将会影响其他的对象。
以上是go語言中切片和陣列是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!