我们知道数组定义好之后其长度就无法再修改,但是,在实际开发过程中,有时候我们并不知道需要多大的数组,我们期望数组的长度是可变的,
在 Go 中有一种数据结构切片(Slice) 解决了这个问题,它是可变长的,可以随时向Slice 里面添加数据。
1 什么是切片(Slice)
在 Go 源码中是这样定义切片的,源码地址:https://github.com/golang/go/blob/master/src/runtime/slice.go
type slice struct { array unsafe.Pointer len int cap int }
从源码中我们可以看到 Slice 也是一种结构体,这个结构体的名字是:Slice,这个结构体包含三个属性:array、len、cap。
第1个属性是指向底层数组的指针(Pointer),指向数组中 Slice 开始的位置;
第2个属性是切片中元素的个数(len),也就是这个 Slice 的长度;
第3个属性是这个切片的容量(cap),也就是 Slice 从开始位置到底层数组最后位置的长度;
2 切片的创建
2.1 切片的创建方式有很多种,一个比较通用的创建方式,使用 Go 的内置函数 make() 创建
package main import "fmt" func main() { var s1 []int = make([]int,5,8) var s2 []int = make([]int,8) fmt.Println(s1, s2) }
输出结果
[[email protected]_81_181_centos golang]# go run slice01.go [0 0 0 0 0] [0 0 0 0 0 0 0 0] [[email protected]_81_181_centos golang]#
make() 函数创建切片,需要提供三个参数,切片的类型、切片的长度、切片的容量。其中第3个参数是可选的,如果第三个参数不提供的话,
则代表创建的是满容切片,也就是长度和容量相等。另外切片也可以通过类型自动推导,省去类型定义和 var 关键字。比如:
package main import "fmt" func main() { var s1 []int = make([]int, 5, 8) s2 := make([]int, 8) fmt.Println(s1, s2) }
另外,我们可以使用 len()、cap() 函数获取切片的长度和容量
package main import "fmt" func main() { 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) }
输出结果
[[email protected]_81_181_centos golang]# go run slice01.go len=3 cap=5 slice=[0 0 0] [[email protected]_81_181_centos golang]#
2.2 用已有的数组生成切片
package main import "fmt" func main() { // 1.通过数组生成切片 // 定义一个数组 arr1 := [8]int{1,2,3,4,5,6,7,8} fmt.Println(arr1) // 定义一个切片 s1 := arr1[1:4] fmt.Println(s1) }
输出结果
[[email protected]_81_181_centos golang]# go run slice01.go [1 2 3 4 5 6 7 8] [2 3 4] [[email protected]_81_181_centos golang]#
2.3 用已有的切片生成切片
package main import "fmt" func main() { s := []int{1,2,3} s1 := s[1:3] // s1 为 [2,3] s2 := s1[1:2] fmt.Println(s2) }
输出结果
[[email protected]_81_181_centos golang]# go run slice01.go [3] [[email protected]_81_181_centos golang]#
3 切片的初始化
使用 make() 函数创建的切片是零值切片,Go 语言还提供了另外一种创建切片的方法,允许我们给它赋初值,使用这种方式创建的切片
是满容的。
3.1 通过数组初始化切片:
s := []int{1,2,3,4,5}
直接初始化切片s
s := arr[:]
初始化切片s,是数组 arr 的引用
s := arr[startIndex:endIndex]
从已有数组中创建一个新的切片,新切片元素是从数组 arr 下标 startIndex 到 endIndex-1
s := [startIndex:]
缺省 endIndex 表示一直取到数组的最后一个元素
s :=arr[:endIndex]
缺省 startIndex 表示从数组的第一个元素开始取值
3.2 通过切片初始化切片
s1 := s[startIndex:endIndex]
4 切片的遍历
切片在遍历上的方式和数组是一样的,支持 for 和 使用 range 关键字遍历
package main import "fmt" func main() { s := []int{1,2,3,4,5} // for 遍历 for i := 0;i < len(s);i++ { fmt.Println(s[i]) } // 使用 range 关键字 for index,value := range s { fmt.Println(index, value) } }
输出结果
[[email protected]_81_181_centos golang]# go run slice01.go 1 2 3 4 5 0 1 1 2 2 3 3 4 4 5 [[email protected]_81_181_centos golang]#
5 切片的扩容(追加)
我们知道切片的长度是可变化的,这个可变其实就是追加操作(append)导致的,我们使用 append 操作追加元素到切片时,如果容
量不够,则会创建新的切片,意味着内存地址也会发生变化,如果底层数组没有扩容,那么追加前后的两个切片共享底层数组,当底
层数组是共享的,一个切片的内容变化会影响到另一个切片的内容。如果底层数组扩容了,那么追加前后的两个切片是不共享底层数组
的。
package main import "fmt" func main() { // 定义切片s1 且切片s1是满容的 s1 := []int{1,2,3,4,5} // 打印切片s1 fmt.Printf("内存地址:%p \t\t长度:%v \t\t容量%v \t\t包含的元素:%v\n",s1,len(s1),cap(s1),s1) // 对满容的切片追加元素 s2 := append(s1,6) // 打印切片s2 fmt.Printf("内存地址:%p \t\t长度:%v \t\t容量%v \t\t包含的元素:%v\n",s2,len(s2),cap(s2),s2) // 修改s2的值 s2[1] = 10 fmt.Println(s1,s2) // 对没有满容的切片追加元素 s3 := append(s2,7) // 打印切片s3 fmt.Printf("内存地址:%p \t\t长度:%v \t\t容量%v \t\t包含的元素:%v\n",s3,len(s3),cap(s3),s3) // 修改s3的值 s3[1] = 20 fmt.Println(s2,s3) }
输出结果
[[email protected]_81_181_centos golang]# go run slice01.go 内存地址:0xc420010150 长度:5 容量5 包含的元素:[1 2 3 4 5] 内存地址:0xc420042050 长度:6 容量10 包含的元素:[1 2 3 4 5 6] [1 2 3 4 5] [1 10 3 4 5 6] 内存地址:0xc420042050 长度:7 容量10 包含的元素:[1 10 3 4 5 6 7] [1 20 3 4 5 6] [1 20 3 4 5 6 7] [[email protected]_81_181_centos golang]#
我们可以看到输出结果中 s1、s2 的内存地址发生了变化,是因为我们将元素 6 追加至切片 s1 中,超过了切片的最大容量 5 ,会创
建一个新的切片并将 s1 原有的元素拷贝一份至新的切片中,并且我们修改 s2 的值,发现 s1 并没有发生变化,说明s1、s2不在共享底
层数组。
扩容后的切片 s2 容量为 10 ,我们再向 s2 追加元素 7 后,没有超过 s2 的最大容量,且s2、s3 的内存地址一致,并且修改 s3 的值,
s2也会发生变化,说明s2、s3 共享底层数组。
所以,初始化切片的时候给出了足够的容量,append 操作的时候不会创建新的切片。
这里可能还会有一个疑问,为什么追加一个元素后容量由原来的 5 变成了 10?这里牵涉到 Slice 的扩容机制,可以参考这篇文章写得非
常详细:http://blog.realjf.com/archives/217
原文地址:https://www.cnblogs.com/leeyongbard/p/10046550.html