go语言数组和切片学习
- 数组和切片的概念
数组类型的值(以下简称数组)的长度是固定的,而切片类型的值(以下简称切片)是可变长的。数组的长度在声明它的时候就必须给定,并且之后不会再改变。可以说,数组的长度是其类型的一部分。比如,[1]string和[2]string就是两个不同的数组类型。而切片的类型字面量中只有元素的类型,而没有长度。切片的长度可以自动地随着其中元素数量的增长而增长,但不会随着元素数量的减少而减小。比如[s]string{"a","b","c"}是长度为3的数组,[]string{"a","b","c"}是string类型的切片。 - 切片和数组的区别:
切片对数组进行了一层简单的封装,内部包含一个数组,可以叫做切片的底层数组,切片就可以看做是对数组一个连续片段的引用。也正因为如此,Go 语言的切片类型属于引用类型,同属引用类型的还有字典类型、通道类型、函数类型等;而 Go 语言的数组类型则属于值类型,同属值类型的有基础数据类型以及结构体类型。注意,Go 语言里不存在像 Java 等编程语言中令人困惑的“传值或传引用”问题。在 Go 语言中,我们判断所谓的“传值”或者“传引用”只要看被传递的值的类型就好了。如果传递的值是引用类型的,那么就是“传引用”。如果传递的值是值类型的,那么就是“传值”。从传递成本的角度讲,引用类型的值往往要比值类型的值低很多。我们在数组和切片之上都可以应用索引表达式,得到的都会是某个元素。我们在它们之上也都可以应用切片表达式,也都会得到一个新的切片。
- 切片的长度和容量
- 长度指的是切片的长度,容量指的是切片底层数组的长度。
- 第一种情况:用make()创建切片
//s1 := make([]int, 5)
fmt.Printf("The length of s1: %d\n", len(s1))
fmt.Printf("The capacity of s1: %d\n", cap(s1))
fmt.Printf("The value of s1: %d\n", s1)
//运行结果:
//The length of s1: 5
//The capacity of s1: 5
//The value of s1: [0 0 0 0 0]
//如果只有一个5代表长度和容量都为5
s2 := make([]int, 5, 8)
fmt.Printf("The length of s2: %d\n", len(s2))
fmt.Printf("The capacity of s2: %d\n", cap(s2))
fmt.Printf("The value of s2: %d\n", s2)
fmt.Println()
//运行结果
// The length of s2: 5
// The capacity of s2: 8
// The value of s2: [0 0 0 0 0]
//第一个5代表长度,第二个8代表容量。
- 第二种情况:用[a:b]创建切片
s2[6] = 8 //会报错 :panic: runtime error: index ouof range [6] with length 5
s3 = s2[2:6] //长度 = 6-2=4,容量 = 8-2 = 6
-
总结:
s := make(int[] ,a,b)会创建一个容量为a的切片s,同时会创建s的一个的底层数组,长度为b,s就像是这个底层数组的滑动窗口。如果用s2 :=s[2,4],那么s2的底层数组与s的底层数组相同,长度为b-2。s2容量为2,对底层数组元素的改动会反应到切片上。另外,切片代表的窗口无法向左扩展,只能向有扩展,直到底层数组的末尾。 -
切片容量增长方式
- 如果切片增长超过切片的容量,只能使用append()追加数据。
- append()会生成一个容量更大的切片,然后将把原有的元素和新元素一并拷贝到新切片中。在一般的情况下,新切片的容量(以下简称新容量)将会是原切片容量(以下简称原容量)的 2 倍。
- 当原切片的长度(以下简称原长度)大于或等于1024时,Go 语言将会以原容量的1.25倍作为新容量的基准(以下新容量基准)。新容量基准会被调整(不断地与1.25相乘),直到结果不小于原长度与要追加的元素数量之和(以下简称新长度)。最终,新容量往往会比新长度大一些,当然,相等也是可能的。
- 如果我们一次追加的元素过多,以至于使新长度比原容量的 2 倍还要大,那么新容量就会以新长度为基准。注意,与前面那种情况一样,最终的新容量在很多时候都要比新容量基准更大一些。更多细节可参见runtime包中 slice.go 文件里的growslice及相关函数的具体实现。
- 切片底层数组注意事项
1.底层数组的值改变会影响到所以以它为底层的切片。同理切片对底层数组的影响也会反映到其它切片。
2.切片扩容后会生成一个新的切片和底层数组,数据拷贝原切片。
arr := [3]int{1,2,3}
s1:=arr[:]
fmt.Printf("arr[1]的地址 %p ",&arr[1])
fmt.Printf("s1[1]的地址:%p",&s1[1])
s1 = append(s1,4,5,6,7,8)
fmt.Printf("扩容后:%p",&s1[1])
fmt.Println(s1)
s1[1] = 100
fmt.Println(arr)
//运行结果:
// arr[1]的地址 0xc000010328 s1[1]的地址:0xc000010328扩容后:0xc00000e488[1 2 3 4 5 6 7 8]
//[1 2 3]