这是我参与「第五届青训营 」笔记创作活动的第17天。
数组
Golang里的数组和C++里的有很大不同,C语言的数组就是一个指向数组首元素的指针,其大小可以动态分配。而Golang的数组不再是指针了,而是值类型,就是数组本体,大小为每个元素大小乘以数组大小;也不能动态分配,必须一开始就定义一个常量大小。
func F(a [3]int) {
a[0] = 1
fmt.Printf("F: %v %p\n", a, &a)
}
func main() {
var o [3]int
o[0] = 2
F(o)
fmt.Printf("main: %v %p\n", o, &o)
}
比如说上面这个例子,在主程序中调用了F,往里面传入了一个大小为3的数组。如果是C语言的话,传入的本质上是int指针,F的形参a的指向和实参o的指向是一样的,因此修改了a数组,o数组的值也变了。而在Go语言中进行的是值传递,相当于把o复制了一份传入了F,F里无论对a怎么修改,都影响不到o,因此输出是:
F: [1 0 0] 0xc000010150
main: [2 0 0] 0xc000010138
切片
而切片则更加类似于C语言的数组,它是引用类型的,相当于一个指向底层数组的指针,并且记录了其容量(最大大小)与当前大小。
func main() {
s := make([]int, 3, 5)
s[1] = 2
fmt.Println(s, len(s), cap(s))
}
切片是引用类型,需要用make来初始化,第一个参数是切片的类型,第二个参数是切片初始的长度,第三个参数是切片的容量。对切片的赋值就像对普通数组一样。以上程序输出:
[0 2 0] 3 5
可以通过append操作往切片末尾追加元素,且切片会自动维护其长度和容量。如果长度超过容量,那么会触发扩容。扩容的大致流程就是,分配一个相当于原来容量两倍的底层数组,然后把原先的元素复制过去,可以证明复杂度是均摊O(N)的。证明方法是,把负的切片剩余空间(容量减去长度)作为观测变量,不触发扩容的append操作代价为1,会使剩余空间减1,均摊复杂度为1+(-1)*(-1)=2;扩容操作代价为k(原容量),会使剩余空间从0变为k,均摊复杂度为k+(-1)*k=0。
由于扩容操作的存在,append返回的不一定是原切片,这会产生一些问题,详见我之前写的文章。 juejin.cn/post/718961…