失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 【Go】Go 语言切片(Slice)

【Go】Go 语言切片(Slice)

时间:2021-10-01 02:30:40

相关推荐

【Go】Go 语言切片(Slice)

文章目录

一、切片二、声明切片方法1方法2总结:创建切片的各种方式三、切片初始化1. 声明的同时初始化2. 用数组初始化切片3. 切片的内存布局:读写操作实际目标是底层数组,只需注意索引号的差别。(本质:切片是数组的一个引用)这是因为,我们直接创建 slice 对象时,系统会自动分配底层数组还可用指针直接访问底层数组,退化成普通数组操作四、一些复杂类型切片1. [][]T,是指元素类型为 []T 的切片。2. 结构体数组 / 切片五、len()、cap()、append()、copy()1. len() 和 cap() 函数2. append() 函数(1)用 append 内置函数实现切片追加,实例:(2)append 函数原理 :向 slice 尾部添加数据,返回新的 slice 对象。(3)若给切片 append 的元素数量超出原 slice.cap 限制,就会重新给 slice 分配底层数组,并进行扩容:3. copy() 函数(切片的深拷贝)六、切片遍历七、字符串和切片(string and slice)1. string 底层就是一个 byte 的数组,因此,也可以进行切片操作。2. string本身是不可变的,因此要改变string中的字符。需要如下操作:八、对切片[x:y:z] 两个冒号的理解1. 常规切片截取(一个冒号)2. 两个冒号的截取参考链接

一、切片

Go 语言切片是对数组的一种抽象。

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

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

切片:切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。切片的长度可以改变,因此,切片相当于一个可变的数组。切片遍历方式和数组一样,可以用 len() 求长度。表示可用元素数量,读写操作不能超过该限制。cap 可以求出 slice 最大扩张容量,不能超出数组限制。0 <= len(slice) <= len(array),其中array是slice引用的数组。切片的定义:var 切片变量名 []类型,比如var str []stringvar arr []int。如果 slice == nil,那么 len、cap 结果都等于 0。

切片 Slice 在源码中的数据结构定义如下:

type slice struct {array unsafe.Pointer //一个指向数组的指针len intcap int}

切片的结构体由3部分构成,Pointer 是指向一个数组的指针,len 代表当前切片的长度,cap 是当前切片的容量。cap 总是大于等于 len 的。

总结:

Go 语言中的切片类型是从数组类型基础上发展出来的新类型,当声明一个数组时,不指定该数组长度,则该类型为切片(“动态数组”),切片有自己独立的内部结构字段(len, cap, array pointer),并于其引用的底层数组共用存储空间。


二、声明切片

方法1

你可以声明一个未指定大小的数组来定义切片,切片声明时不需要说明长度([]没有声明长度,说明这是一个切片,而不是一个数组。因为数组声明是必须指定长度的。):

var identifier []type

如上这种形式的只声明不初始化,这时切片默认初始化为nillen=0 cap=0 slice=[]

之所以为nil,是因为没有分配存储空间

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

package mainimport "fmt"func main() {var numbers []intprintSlice(numbers)if numbers == nil {fmt.Printf("切片是空的")}}func printSlice(x []int) {fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)}

输出结果:

len=0 cap=0 slice=[]切片是空的

多一嘴:

nil 切片被用在很多标准库和内置函数中,描述一个不存在的切片的时候,就需要用到 nil 切片。比如函数在发生异常的时候,返回的切片就是 nil 切片。nil 切片的指针指向 nil。

空切片一般会用来表示一个空的集合。比如数据库查询,一条结果也没有查到,那么就可以返回一个空切片。

方法2

如果你想声明一个拥有初始长度或规定容量的切片(可以指定切片的长度和容量),可以使用make()函数来创建切片:

var slice1 []type = make([]type, length, capacity)也可以简写为slice1 := make([]type, length, capacity)

这里 length 是数组的长度并且也是切片的初始长度。

容量 capacity 为可选参数(可选的意思是可以缺省,如果不指定capacity,则capacity默认等于length)。

make 创建的切片与其底层数组:

实例:缺省 capacity

package mainimport "fmt"func main() {numbers := make([]int, 3)printSlice(numbers)}func printSlice(x []int) {fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)}

输出结果:

len=3 cap=3 slice=[0 0 0]

如上这样创建切片、不初始化,那么切片被系统自动初始化为0。(而不是 nil )

之所以不是nil,是因为 make 函数为其分配了内存空间

总结:创建切片的各种方式

实例:

package mainimport "fmt"func main() {//1.声明切片var s1 []intif s1 == nil {fmt.Println("s1是空")} else {fmt.Println("s1不是空")}// 2.make()创建var s2 []int = make([]int, 0)var s3 []int = make([]int, 0, 0)if s2 == nil {fmt.Println("s2是空")} else {fmt.Println("s2不是空")}if s3 == nil {fmt.Println("s3是空")} else {fmt.Println("s3不是空")}fmt.Println(s1, s2, s3)// 3.:=s4 := []int{}s5 := []int{1, 2, 3}fmt.Println(s4, s5)if s4 == nil {fmt.Println("s4是空")} else {fmt.Println("s4不是空")}// 4.从数组切片arr := [5]int{1, 2, 3, 4, 5} //数组var s6 []int //切片s6 = arr[1:4]fmt.Println(s6)}

输出结果:

s1是空s2不是空s3不是空[] [] [][] [1 2 3]s4不是空[2 3 4]

有四种创建切片的方法:

常规声明make() 函数创建赋值符 := 创建引用数组

其中,只有 “常规声明” 却不初始化的切片被系统默认为nil(没有内存空间)。用 make() 函数或 := 创建却不初始化的切片为空切片(拥有内存空间,只是没有元素),如果有元素的话会被系统默认初始化为 0 。这是因为 “常规声明” 不会为切片分配存储空间,而其他方法会分配。

空切片和nil切片的区别在于,空切片指向的地址不是nil,指向的是一个内存地址,但是它没有分配任何内存空间,即底层元素包含0个元素。

最后需要说明的一点是。不管是使用 nil 切片还是空切片,对其调用内置函数 append,len 和 cap 的效果都是一样的。


三、切片初始化

1. 声明的同时初始化

s := []int{1, 2, 3} //一句代码完成声明和初始化两个工作

直接完成了声明和初始化切片,[] 表示是切片类型,{1,2,3} 初始化值依次是 1,2,3,其 cap=len=3:len=3 cap=3 slice=[1 2 3]

赋值符初始化的切片与其底层数组:

2. 用数组初始化切片

初始化切片 s,是数组 arr 的引用:

//用数组arr的所有值初始化切片s := arr[:]

将 arr 中从下标startIndexendIndex-1下的元素创建为一个新的切片:

s := arr[startIndex:endIndex]

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

s := arr[startIndex:]

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

s := arr[:endIndex]

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

s1 := s[startIndex:endIndex]

引用数组元素初始化的切片与其底层数组:

用数组初始化切片的方法总结:

全局变量:var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} //这是一个数组var slice0 []int = arr[start:end] var slice1 []int = arr[:end] var slice2 []int = arr[start:] var slice3 []int = arr[:] var slice4 = arr[:len(arr)-1]//去掉切片的最后一个元素局部变量:arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0} //这是一个数组slice5 := arr[start:end]slice6 := arr[:end] slice7 := arr[start:]slice8 := arr[:] slice9 := arr[:len(arr)-1] //去掉切片的最后一个元素

3. 切片的内存布局:

读写操作实际目标是底层数组,只需注意索引号的差别。(本质:切片是数组的一个引用)

package mainimport ("fmt")func main() {data := [...]int{0, 1, 2, 3, 4, 5}s := data[2:4]s[0] += 100s[1] += 200fmt.Println(s)fmt.Println(data)}

输出结果:

[102 203][0 1 102 203 4 5]

可见:对切片内容的改变实际上改变的是它所引用的数组。

切片就像一个傀儡、一个指针、一个虚构,对它的操作就是对原数组的操作。切片和它所引用的数组是一体的,虽然我们看到的是一个切片,其实它还是底层的数组。它们两者是统一的,你就把切片当成一个原数组的一段就行。

这时有同学就有疑问了,前面的那么多创建切片的方式,并不都是通过引用数组得来的呀,大部分都是直接创建切片的呀?

这是因为,我们直接创建 slice 对象时,系统会自动分配底层数组

实例:

package mainimport "fmt"func main() {s1 := []int{0, 1, 2, 3, 8: 100} // 通过初始化表达式构造,可使用索引号。fmt.Println(s1, len(s1), cap(s1))s2 := make([]int, 6, 8) // 使用 make 创建,指定 len 和 cap 值。fmt.Println(s2, len(s2), cap(s2))s3 := make([]int, 6) // 省略 cap,相当于 cap = len。fmt.Println(s3, len(s3), cap(s3))}

输出结果:

[0 1 2 3 0 0 0 0 100] 9 9[0 0 0 0 0 0] 6 8[0 0 0 0 0 0] 6 6

从这个实例可以看到,你创建的的确是切片,但是它还是数组的形式呀,并不是其他形式。

所以说切片的本质是数组,但是并不是数组,它只是引用了数组的一段。

你创建了一个切片,系统会自动为你创建一个底层数组,然后引用这个底层数组生成一个切片。

你觉得你操作的是切片本身,但实际上操作的是它所依托的那个底层的数组。

既然访问的还是底层数组,那我们为什么不直接操作数组呢?

这是因为切片长度可变的灵活性:使用 make 动态创建slice,避免了数组必须用常量做长度的麻烦。

比如:切片resize(调整大小)

package mainimport ("fmt")func main() {var a = []int{1, 3, 4, 5}fmt.Printf("slice a : %v , len(a) : %v\n", a, len(a))b := a[1:2]fmt.Printf("slice b : %v , len(b) : %v\n", b, len(b))c := b[0:3]fmt.Printf("slice c : %v , len(c) : %v\n", c, len(c))}

输出结果:

slice a : [1 3 4 5] , len(a) : 4slice b : [3] , len(b) : 1slice c : [3 4 5] , len(c) : 3

其原理仍然是:读写操作实际目标是底层数组。

但是这里用切片的话,它的长度就非常灵活。

还可用指针直接访问底层数组,退化成普通数组操作

package mainimport "fmt"func main() {s := []int{0, 1, 2, 3}p := &s[2] // *int, 获取底层数组元素指针。*p += 100fmt.Println(s)}

输出结果:

[0 1 102 3]


四、一些复杂类型切片

1. [][]T,是指元素类型为 []T 的切片。

实例:

package mainimport ("fmt")func main() {data := [][]int{//[]int类型的切片[]int{1, 2, 3},//初始化值[]int{100, 200},[]int{11, 22, 33, 44},}fmt.Println(data)}

输出结果:

[[1 2 3] [100 200] [11 22 33 44]]

2. 结构体数组 / 切片

可直接修改 struct array/slice 成员:

package mainimport ("fmt")func main() {d := [5]struct {//结构体数组x int}{} //未初始化s := d[:] //切片d[1].x = 10s[2].x = 20fmt.Println(d)fmt.Printf("%p, %p\n", &d, &d[0])}

输出结果:

[{0} {10} {20} {0} {0}]0xc00000c540, 0xc00000c540


五、len()、cap()、append()、copy()

1. len() 和 cap() 函数

可以使用 len() 方法获取切片长度。

计算切片容量的方法 cap() 可以测量切片最长可以达到多少。

以下为具体实例:

package mainimport "fmt"func main() {var numbers = make([]int, 3, 5)printSlice(numbers)}func printSlice(x []int) {fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)}

输出结果:

len=3 cap=5 slice=[0 0 0]

2. append() 函数

(1)用 append 内置函数实现切片追加,实例:

package mainimport ("fmt")func main() {var a = []int{1, 2, 3}fmt.Printf("slice a : %v\n", a)var b = []int{4, 5, 6}fmt.Printf("slice b : %v\n", b)c := append(a, b...)fmt.Printf("slice c : %v\n", c)d := append(c, 7)fmt.Printf("slice d : %v\n", d)e := append(d, 8, 9, 10)fmt.Printf("slice e : %v\n", e)}

输出结果:

slice a : [1 2 3]slice b : [4 5 6]slice c : [1 2 3 4 5 6]slice d : [1 2 3 4 5 6 7]slice e : [1 2 3 4 5 6 7 8 9 10]

(2)append 函数原理 :向 slice 尾部添加数据,返回新的 slice 对象。

package mainimport ("fmt")func main() {s1 := make([]int, 0, 5)fmt.Printf("%p\n", &s1)fmt.Println(s1)s2 := append(s1, 1)fmt.Printf("%p\n", &s2)fmt.Println(s2)}

输出结果:

0xc000004078[]0xc0000040a8[1]

(3)若给切片 append 的元素数量超出原 slice.cap 限制,就会重新给 slice 分配底层数组,并进行扩容:

package mainimport ("fmt")func main() {data := [...]int{0, 1, 2, 3, 4, 10: 0} //数组s := data[:2:3]fmt.Println(s)fmt.Println(len(s), cap(s))s = append(s, 100, 200, 300) // 一次 append 三个值,超出 s.cap 限制。fmt.Println(s, data) // 重新分配底层数组,与原数组无关。fmt.Println(&s[0], &data[0]) // 比对底层数组起始指针。}

输出结果:

[0 1]2 3[0 1 100 200 300] [0 1 2 3 4 0 0 0 0 0 0]0xc00000c570 0xc00004a060

从输出结果可以看出:append后的 s 被重新分配了底层数组(也就是说 s 的底层数组不再是 data,那么修改 s 的值不会再影响 data,它们不再有关联),并把原数组中的值拷贝到新数组中。这是因为超出了原切片的容量。在上例中,如果只追加一个值,则不会超过 s.cap 限制,也就不会重新分配。

切片的自动扩容策略是这样的:(文章 简单说说go语言Slice的底层实现 通过分析源码对这一点提出了质疑)

通常以 2 倍容量进行扩容,并重新分配底层数组(新底层数组的容量也变大)。

如果切片的容量小于 1024 个元素,扩容的时候就翻倍增加容量。一旦元素个数超过 1024 个元素,那么增长因子就变成 1.25 ,即每次增加原来容量的四分之一。

注意:扩容扩大的容量都是针对原来的容量而言的,而不是针对原来数组的长度而言的。

所以,在大批量添加数据时,建议一次性分配足够大的空间,以减少内存分配和数据复制开销。或初始化足够长的 len 属性,改用索引号进行操作。

及时释放不再使用的 slice 对象,避免持有过期数组,造成 GC 无法回收。

slice中 cap 重新分配规律:

package mainimport ("fmt")func main() {s := make([]int, 0, 1)fmt.Println(s)c := cap(s) //计算容量fmt.Println(c)for i := 0; i < 50; i++ {s = append(s, i)//按理说 append 第2个元素时就超出了cap,这时会重新分配底层数组来扩大capif n := cap(s); n > c {fmt.Printf("cap: %d -> %d\n", c, n)c = n}}}

输出结果:

[]1cap: 1 -> 2cap: 2 -> 4cap: 4 -> 8cap: 8 -> 16cap: 16 -> 32cap: 32 -> 64

我们可以发现,通常以 2 倍的 cap 重新分配。

提一嘴哈,如果给切片 append 元素时,不超切片容量就没事,操作的还是原数组:

package mainimport ("fmt")func main() {data := [...]int{0, 1, 2, 3, 4, 10: 0} //数组s := data[:2:5] //将切片容量扩大到5fmt.Println(s)fmt.Println(len(s), cap(s))s = append(s, 100, 200, 300) // 一次 append 三个值,这次没超出 s.cap 限制。fmt.Println(s, data) fmt.Println(&s[0], &data[0]) // 比对底层数组起始指针}

输出结果:

[0 1]2 5[0 1 100 200 300] [0 1 100 200 300 0 0 0 0 0 0]0xc00004a060 0xc00004a060

3. copy() 函数(切片的深拷贝)

切片的拷贝分为2种,一种是浅拷贝,一种是深拷贝。

浅拷贝:源切片和目的切片共享同一底层数组空间,源切片修改,目的切片同样被修改。(赋值符实现)

深拷贝:源切片和目的切片各自都有彼此独立的底层数组空间,各自的修改,彼此不受影响。(使用内置函数copy()函数实现)

以下通过具体实例来说明:

浅拷贝:源切片和目的切片共享同一底层数组空间

package mainimport "fmt"func main(){slice1 := make([]int, 5, 5)slice2 := slice1slice1[1] = 1fmt.Println(slice1) //[0 1 0 0 0]fmt.Println(slice2) //[0 1 0 0 0]}

深拷贝:源切片和目的切片各自都有彼此独立的底层数组空间

package mainimport "fmt"func main() {slice1 := make([]int, 5, 5)slice1[0] = 9fmt.Println(slice1)slice2 := make([]int, 4, 4)slice3 := make([]int, 5, 5)fmt.Println(slice2)fmt.Println(slice3)//拷贝fmt.Println(copy(slice2, slice1)) //4fmt.Println(copy(slice3, slice1)) //5//独立修改slice2[1] = 2slice3[1] = 3fmt.Println(slice1) //[9 0 0 0 0 0]fmt.Println(slice2) //[9 2 0 0]fmt.Println(slice3) //[9 3 0 0 0]}

输出结果:

[9 0 0 0 0][0 0 0 0][0 0 0 0 0]45[9 0 0 0 0][9 2 0 0][9 3 0 0 0]

copy 函数的原理:copy 函数在两个 slice 间复制数据,复制长度以 len 小的为准。两个 slice 可指向同一底层数组,允许元素区间重叠。

下面的三个实例描述了深拷贝切片的 copy 方法的使用:

实例 1

如果想增加切片的容量,我们可以创建一个新的更大的切片把原切片的内容都拷贝过来

package mainimport "fmt"func main() {var numbers []intprintSlice(numbers)numbers = append(numbers, 0, 1, 2, 3, 4)printSlice(numbers)/* 创建切片 numbers1 是之前切片的两倍容量*/numbers1 := make([]int, len(numbers), (cap(numbers))*2)printSlice(numbers1)/* 拷贝 numbers 的内容到 numbers1 */copy(numbers1, numbers)printSlice(numbers1)}func printSlice(x []int) {fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)}

输出结果:

len=0 cap=0 slice=[]len=5 cap=6 slice=[0 1 2 3 4]len=5 cap=12 slice=[0 0 0 0 0]len=5 cap=12 slice=[0 1 2 3 4]

实例 2

package mainimport ("fmt")func main() {s1 := []int{1, 2, 3, 4, 5}fmt.Printf("slice s1 : %v\n", s1)s2 := make([]int, 10)fmt.Printf("slice s2 : %v\n", s2)copy(s2, s1)fmt.Printf("copied slice s1 : %v\n", s1)fmt.Printf("copied slice s2 : %v\n", s2)}

输出结果:

slice s1 : [1 2 3 4 5]slice s2 : [0 0 0 0 0 0 0 0 0 0]copied slice s1 : [1 2 3 4 5]copied slice s2 : [1 2 3 4 5 0 0 0 0 0]

实例 3

package mainimport "fmt"func main() {s := []int{1, 2, 3, 4}var s1 []intcopy(s1, s)fmt.Println(s1) // []fmt.Println(s) // [1 2 3 4]s1 = make([]int, 2)count := copy(s1, s)fmt.Println(s1) // [1 2]fmt.Println(s)// [1 2 3 4]fmt.Println(count) // 2s1[0] = 5fmt.Println(s1) // [5 2]fmt.Println(s) // [1 2 3 4]}

输出结果:

[][1 2 3 4][1 2][1 2 3 4]2[5 2][1 2 3 4]

从该例可知,copy 函数只会拷贝目标切片的长度个元素,并且 copy 后两个切片是互相没有影响的。

实例 2 和 3 说明 copy 函数在两个 slice 间复制数据,复制长度以 len 小的为准。

实例 4

package mainimport ("fmt")func main() {data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}fmt.Println("array data : ", data)s1 := data[8:]s2 := data[:5]fmt.Printf("slice s1 : %v\n", s1)fmt.Printf("slice s2 : %v\n", s2)copy(s2, s1)fmt.Printf("copied slice s1 : %v\n", s1)fmt.Printf("copied slice s2 : %v\n", s2)fmt.Println("last array data : ", data)}

输出结果:

array data : [0 1 2 3 4 5 6 7 8 9]slice s1 : [8 9]slice s2 : [0 1 2 3 4]copied slice s1 : [8 9]copied slice s2 : [8 9 2 3 4]last array data : [8 9 2 3 4 5 6 7 8 9]

应及时将所需数据copy 到较小的 slice,以便释放超大号底层数组内存。


六、切片遍历

import ("fmt")func main() {data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}slice := data[:]for index, value := range slice {fmt.Printf("inde : %v , value : %v\n", index, value)}}

输出结果:

inde : 0 , value : 0inde : 1 , value : 1inde : 2 , value : 2inde : 3 , value : 3inde : 4 , value : 4inde : 5 , value : 5inde : 6 , value : 6inde : 7 , value : 7inde : 8 , value : 8inde : 9 , value : 9


七、字符串和切片(string and slice)

1. string 底层就是一个 byte 的数组,因此,也可以进行切片操作。

对字符串进行切片操作:

package mainimport ("fmt")func main() {str := "hello world"s1 := str[0:5]fmt.Println(s1)s2 := str[6:]fmt.Println(s2)}

输出结果:

helloworld

2. string本身是不可变的,因此要改变string中的字符。需要如下操作:

现在要改变英文字符串 “Hello world” 中的内容:

package mainimport ("fmt")func main() {str := "Hello world"s := []byte(str) //将字符串类型转换成一个切片,中文字符需要用[]rune(str)fmt.Println(s)s[6] = 'G's = s[:8]s = append(s, '!')fmt.Println(s)str = string(s) //将切片转换成字符串fmt.Println(str)}

输出结果:

[72 101 108 108 111 32 119 111 114 108 100][72 101 108 108 111 32 71 111 33]Hello Go!

改变中文字符串的内容:

package mainimport ("fmt")func main() {str := "你好,世界!hello world!"s := []rune(str)s[3] = '够's[4] = '浪's[12] = 'g's = s[:14]str = string(s)fmt.Println(str)}

输出结果:

你好,够浪!hello go

多一嘴:

数组or切片转字符串:

strings.Replace(strings.Trim(fmt.Sprint(array_or_slice), "[]"), " ", ",", -1)


八、对切片[x:y:z] 两个冒号的理解

1. 常规切片截取(一个冒号)

可以通过设置下限及上限来设置截取切片[lower_bound:upper_bound]

截取的内容是下标从lower_boundupper_bound-1,示例如下:

package mainimport "fmt"func main() {/* 创建切片 */numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}printSlice(numbers)/* 打印原始切片 */fmt.Println("numbers ==", numbers)/* 打印子切片从索引1(包含) 到索引4(不包含)*/fmt.Println("numbers[1:4] ==", numbers[1:4])/* 默认下限为 0*/fmt.Println("numbers[:3] ==", numbers[:3])/* 默认上限为 len(s)*/fmt.Println("numbers[4:] ==", numbers[4:])numbers1 := make([]int, 0, 5)printSlice(numbers1)/* 打印子切片从索引 0(包含) 到索引 2(不包含) */number2 := numbers[:2]printSlice(number2)/* 打印子切片从索引 2(包含) 到索引 5(不包含) */number3 := numbers[2:5]printSlice(number3)}func printSlice(x []int) {fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)}

输出结果:

len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8]numbers == [0 1 2 3 4 5 6 7 8]numbers[1:4] == [1 2 3]numbers[:3] == [0 1 2]numbers[4:] == [4 5 6 7 8]len=0 cap=5 slice=[]len=2 cap=9 slice=[0 1]len=3 cap=7 slice=[2 3 4]

2. 两个冒号的截取

package mainimport ("fmt")func main() {slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}d1 := slice[6:8]fmt.Println(d1, len(d1), cap(d1))d2 := slice[:6:8]fmt.Println(d2, len(d2), cap(d2))}

输出结果:

[6 7] 2 4[0 1 2 3 4 5] 6 8

常规切片截取:slice[6:8]表示从下标第6位到第7位,长度len为2, 最大可扩充长度cap为 4(6到9,到尾部是默认的)。

两个冒号的截取:slice[:6:8], slice内容为从 0 到第 5 位,长度 len 为6,最大扩充项 cap 设置为 8(0到7)。

所以说,两个冒号相对于一个冒号多的那个冒号是第二个冒号,这个冒号后的数字用于控制最大容量。

或者说常规切片截取相对于两个冒号的截取少了第二个冒号,省略了,默认第二个冒号后的数字到尾部。

总结:

a[x:y:z]切片内容是:[x:y]切片长度,[x:z]切片容量。长度计算:y-x,容量计算:z-x。


参考链接

Go 语言切片(Slice)切片Slice

如果觉得《【Go】Go 语言切片(Slice)》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。