深入理解go的slice和到底什么时候该用slice?
深入理解go的slice和到底什么时候该用slice前言用过go语言的亲们都知道,slice(中文翻译为切片)在编程中经常用到,它代表变长的序列,序列中每个元素都有相同的类型,类似一个动态数组,利用append可以实现动态增长,利用slice的特性可以很容易的切割slice,它们是怎么实现这些特性的呢?现在我们来探究一下这些特性的本质是什么。 先了解一下slice的特性
s := []int{1,2,3,4,5} fmt.Println(s) // [1 2 3 4 5] 一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已。
s := []int{1,5} s = append(s,6) fmt.Println(s) // [1 2 3 4 5 6] 内置append函数在现有数组的长度 < 1024 时 cap 增长是翻倍的,再往上的增长率则是 1.25,至于为何后面会说。
s := []int{1,5,6} s1 := s[0:2] fmt.Println(s1) // [1 2] s2 := s[4:] fmt.Println(s2) // [5 6] s3 := s[:4] fmt.Println(s3) // [1 2 3 4]
package main import "fmt" func main() { slice_1 := []int{1,5} fmt.Printf("main-->data:t%#vn",slice_1) fmt.Printf("main-->len:t%#vn",len(slice_1)) fmt.Printf("main-->cap:t%#vn",cap(slice_1)) test1(slice_1) fmt.Printf("main-->data:t%#vn",slice_1) test2(&slice_1) fmt.Printf("main-->data:t%#vn",slice_1) } func test1(slice_2 []int) { slice_2[1] = 6666 // 函数外的slice确实有被修改 slice_2 = append(slice_2,8888) // 函数外的不变 fmt.Printf("test1-->data:t%#vn",slice_2) fmt.Printf("test1-->len:t%#vn",len(slice_2)) fmt.Printf("test1-->cap:t%#vn",cap(slice_2)) } func test2(slice_2 *[]int) { // 这样才能修改函数外的slice *slice_2 = append(*slice_2,6666) } 结果: main-->data: []int{1,5} main-->len: 5 main-->cap: 5 test1-->data: []int{1,6666,8888} test1-->len: 6 test1-->cap: 12 main-->data: []int{1,5} main-->data: []int{1,6666} 这里要注意注释的地方,为何slice作为值传递参数,函数外的slice也被更改了?为何在函数内append不能改变函数外的slice?要回da这些问题就得了解slice内部结构,详细请看下面. slice的内部结构其实slice在Go的运行时库中就是一个C语言动态数组的实现,在$GOROOT/src/pkg/runtime/runtime.h中可以看到它的定义: struct Slice { // must not move anything byte* array; // actual data uintgo len; // number of elements uintgo cap; // allocated number of elements }; 这个结构有3个字段,第一个字段表示array的指针,就是真实数据的指针(这个一定要注意),所以才经常说slice是数组的引用,第二个是表示slice的长度,第三个是表示slice的容量,注意:len和cap都不是指针。 现在就可以解释前面的例子slice作为函数参数提出的问题: 函数外的slice叫slice_1,函数的参数叫slice_2,当函数传递slice_1的时候,其实传入的确实是slice_1参数的复制,所以slice_2复制了slise_1,但要注意的是slice_2里存储的数组的指针,所以当在函数内更改数组内容时,函数外的slice_1的内容也改变了。在函数内用append时,append会自动以倍增的方式扩展slice_2的容量,但是扩展也仅仅是函数内slice_2的长度和容量,slice_1的长度和容量是没变的,所以在函数外打印时看起来就是没变。 append的运作机制在对slice进行append等操作时,可能会造成slice的自动扩容。其扩容时的大小增长规则是:
至于为何会这样,你要看一下golang的源码就知道了: https://github.com/golang/go/blob/master/src/runtime/slice.go newcap := old.cap if newcap+newcap < cap { newcap = cap } else { for { if old.len < 1024 { newcap += newcap } else { newcap += newcap / 4 } if newcap >= cap { break } } } 为何不用动态链表实现slice?
什么时候该用slice?在go语言中slice是很灵活的,大部分情况都能表现的很好,但也有特殊情况。 当程序要求slice的容量超大并且需要频繁的更改slice的内容时,就不应该用slice,改用list更合适。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |