爱上开源之golang入门至实战第四章-切片(Slice)
  sRUgsXcNLUQA 2023年11月02日 66 0


前言

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

在go语言里,切边是一个应用极为广泛的数据对象;所以在学习golang的过程中,一定要好好的掌握切片的相关知识。

爱上开源之golang入门至实战第四章-切片(Slice)_golang

4.2.2 切片

Go 语言切片是对数组的抽象。一个slice类型一般写作[]T,其中T代表slice中元素的类型。

slice的语法和数组很像,只是没有固定长度而已。数组和slice之间有着紧密的联系。一个slice是一个轻量级的数据结构,提供了访问数组子序列 (或者全部)元素的功能,slice的**底层引用一个数组对象**。

`slice`(切片)代表变长的序列,序列中每个元素都有相同的类型。

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

4.2.2.1 声明切片

你可以声明一个未指定大小的数组来定义切片:


var slice_01 []type


在语法上和上面的数组比较的相似;差别就在于切片不需要说明长度。

也可以使用 make() 函数来创建切片:


var slice_01 []type = make([]type, len) 或为 slice_01 := make([]type, len)


在切片的make声明和初始话的方式中;也可以指定容量,

比如


make([]T, length, capacity)


其中 capacity 为可选参数。通过上面的方式就可以声明和初始话一个容量大小为capacity,当前长度为length的切片,切片的用法如何使用过python语言的话,这个切片的数据类型可以实现的功能基本上和python语言里slice函数的非常的相似

需要说明,slice 并不是数组或数组指针。它通过内部指针和相关属性引⽤数组⽚段,以实现变⻓⽅案。

runtime.h


#runtime.h struct Slice { // must not move anything byte* array; // actual data uintgo len; // number of elements uintgo cap; // allocated number of elements };


以上是golang里slice的源代码片段;可以通过这个slice的结构体源代码的定义,发现在golang里,slice是通过内部属性和指针的方式,来实现了变长的方案。

• 引⽤类型。但⾃⾝是结构体,值拷⻉传递。

• 属性 len 表⽰可⽤元素数量,读写操作不能超过该限制。

• 属性 cap 表⽰最⼤扩张容量,不能超出数组限制。

• 如果 slice == nil,那么 len、cap 结果都等于 0。

4.2.2.2 切片初始化


slice_01 :=[] int {1,2,3 }


通过初始化语句:=直接进行初始化, 其中[] 表示是切片类型,{1,2,3} 初始化值依次是 1,2,3,其 cap=len=3。通过以上语句,实现了初始化slice_01对象为切片对象,并且长度和容量都为3;其中元素依次为1,2,3的int切片。

4.2.2.3 len和cap

切片是可索引的,并且可以由 len() 方法获取长度。切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。


x := make([]int,3,5) fmt.Printf("len=%d cap=%d slice=%v**\n**",len(x),cap(x),x) ====OUTPUT==== len=3 cap=5 slice=[0 0 0]


一个切片在未初始化之前默认为 nil,长度为 0,如下:


var x []int fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x) ====OUTPUT==== len=0 cap=0 slice=[]


4.2.2.4 slice


s := arr[startIndex:endIndex]


将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片。


s := arr[startIndex:]


默认 endIndex 时将表示一直到arr的最后一个元素。


s := arr[:endIndex]


默认 startIndex 时将表示从 arr 的第一个元素开始。

样例


data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} expression slice len cap ------------+----------------------+------+------- data[:6:8] [0 1 2 3 4 5] 6 8 data[5:] [5 6 7 8 9] 5 5 data[:3] [0 1 2] 3 10 data[:] [0 1 2 3 4 5 6 7 8 9] 10 10 data[:11:12] Error


读写操作实际⺫标是底层数组,只需注意索引号的差别。


data := [...]int{0, 1, 2, 3, 4, 5} s := data[2:4] s[0] += 1020 s[1] += 2020 fmt.Println(s) fmt.Println(data)


输出:


[1022 2023] [0 1 1022 2023 3 4 5]



s1 := s[startIndex:endIndex]


通过切片 s 初始化切片 s1。

对于是基于已有 slice 创建新 slice 对象,特别注意要保证切片的 cap 允许范围内调整属性。


s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} s1 := s[2:5] // [2 3 4]   len=3 cap=8 s2 := s1[2:6:7] // [4 5 6 7] len=3 cap=5 s3 := s2[3:6] // Error


切片操作后新对象依旧指向原底层数组。


s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} s1 := s[2:5] // [2 3 4] s1[2] = 1020 fmt.Println(s1) s2 := s1[2:6] // [1020 5 6 7] s2[0] = 2010 s2[3] = 2020 fmt.Println(s2) fmt.Println(s) ====OUTPUT==== [2 3 1020] [2010 5 6 2020] [2 3 2010] [0 1 2 3 2010 5 6 2020 8 9]


4.2.2.5 append

向 slice 尾部添加数据,返回新的 slice 对象。


s := make([]int, 0, 5) fmt.Printf("%p\n", &s) s2 := append(s, 1) fmt.Printf("%p\n", &s2) fmt.Println(s, s2) ====OUTPUT===== 0xc000004498 0xc0000044c8 [] [1]



data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} s := data[:3] s2 := append(s, 100, 200) // 添加多个值。 fmt.Println(data) fmt.Println(s) fmt.Println(s2) ====OUTPUT===== [0 1 2 100 200 5 6 7 8 9] [0 1 2] [0 1 2 100 200]


⼀旦超出原 slice.cap 限制,就会重新分配底层数组,即便原数组并未填满。


data := [...]int{0, 1, 2, 3, 4, 10: 0} s := data[:2:3] s = append(s, 100, 200) // ⼀次 append 两个值,超出 s.cap 限制。 fmt.Println(s, data) // 重新分配底层数组,与原数组⽆关。 fmt.Println(&s[0], &data[0]) // ⽐对底层数组起始指针。 ====OUTPUT===== [0 1 100 200] [0 1 2 3 4 5 6 7 8 9] 0xc0001e21e0 0xc000022af0


从输出结果可以看出,append 后的 s 重新分配了底层数组,并复制数据。如果只追加⼀个值,则不会超过 s.cap 限制,也就不会重新分配

将上面程序改成下面的


data := [...]int{0, 1, 2, 3, 4, 10: 0} s := data[:2:3] s = append(s, 100) fmt.Println(s, data) fmt.Println(&s[0], &data[0]) s = append(s, 200)   fmt.Println(s, data) // 重新分配底层数组,与原数组⽆关。 fmt.Println(&s[0], &data[0]) // ⽐对底层数组起始指针。 ====OUTPUT===== [0 1 100] [0 1 100 3 4 5 6 7 8 9] 0xc00011e9b0 0xc00011e9b0 [0 1 100 200] [0 1 100 3 4 5 6 7 8 9] 0xc00013dd70 0xc00011e9b0


4.2.2.6 copy

copy 函数可以实现在两个 slice 间复制数据,复制⻓度以 len⼩的为准。两个 slice 可指向同⼀底层数组,允许元素区间重叠。


data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} s := data[8:] s2 := data[:5] copy(s2, s) fmt.Println(s2) fmt.Println(data) ====OUTPUT===== [8 9 2 3 4] [8 9 2 3 4 5 6 7 8 9]


4.2.2.7 扩容

当切片的长度超过容量的情况下,切片会进行扩容操作;通常以 2 倍容量重新分配底层数组。在⼤批量添加数据时,建议⼀次性分配⾜够⼤的空间,以减少内存分配和数据复制开销。或初始化⾜够⻓的 len 属性,改⽤索引号进⾏操作。及时释放不再使⽤的 slice 对象,避免持有过期数组,造成 GC ⽆法回收

结束语

在go语言里,切边是一个应用极为广泛的数据对象;所以在学习golang的过程中,一定要好好的掌握切片的相关知识。

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论