失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 【Go学习笔记】数据类型之切片(slice)

【Go学习笔记】数据类型之切片(slice)

时间:2021-02-21 04:18:54

相关推荐

【Go学习笔记】数据类型之切片(slice)

切片是Go中一种比较特殊的数据结构,这种数据结构更便于使用和管理数据集合。切片是围绕动态数组的概念构建的,可以按需自动增长和缩小。它非常灵活,支持自动扩容。切片的底层一段连续的内存。

切片的内部实现

切片是一个有三个字段的数据结构,分别为地址长度容量,它对底层的数组(内部是通过数组保存数据的)进行了抽象,并提供相关的操作方法。

地址:指向底层数组的指针。

长度:切片可以访问的元素的个数,使用内置函数len(切片名)可以获得。

容量:从切片地址开始到底层数组结尾的长度,使用内置函数cap(切片名)可以获得。

切片的创建和初始化

在Golang中可以通过多种方式创建和初始化切片。可以根据切片所需的容量来决定如何创建切片。

切片的声明

切片的声明格式如下:

var 切片名 []元素类型例如,声明一个地址为nil的整型切片:var myNum []int // 例:声明一个地址为nil的整型切片

单纯声明后的切片并没有分配内存空间,因此地址指向nil,可以称之为nil切片。nil切片的长度和容量都为0,数据结构状态如下:

在Golang中,nil切片很常见,可以使用很多标准库和内置函数。在需要描述一个不存在的切片时,nil切片会很好用。比如,函数要求返回一个切片但是发生异常的时候。

通过make()函数创建切片

使用Golang内置的make()函数,动态创建一个切片,格式如下:

make([]类型, 长度, 容量)a := make([]int, 3, 5) // 定义一个长度为3,容量为5,元素为int类型的切片fmt.Println(a) // [0 0 0] 切片可访问的元素数为3fmt.Println(len(a))// 3fmt.Println(cap(a))// 5fmt.Printf("%#v \n", a) // []int{0, 0, 0}

示例代码中a的内部存储空间已经分配了5个,但实际只是用了3个。容量并不会影响当前元素的个数。

当要创建的切片长度=容量时,可以使用以下格式进行创建:

make([]类型, 长度) // 创建长度=容量的切片 b := make([]int, 3)// 定义一个长度为3,容量为3,元素为int类型的切片fmt.Println(a) // [0 0 0]fmt.Println(len(a))// 3fmt.Println(cap(a))// 3fmt.Printf("%#v \n", a) // []int{0, 0, 0}

创建切片时,长度和容量是可以为0的,此时地址指针不为nil,称为空切片。下图描述了空切片的状态。

通过字面量创建切片

另一种常用的创建切片的方法是使用切片字面量,这种方法和创建数组类似,只是不需要指定[]运算符里的值。初始的长度和容量会基于初始化时提供的元素的个数确定:

// 创建字符串切片// 其长度和容量都是3个元素myStr := []string{"Jack", "Mark", "Nick"}fmt.Printf("myStr = %#v,长度=%d,容量=%d \n", myStr, len(myStr), cap(myStr))// output: myStr = []string{"Jack", "Mark", "Nick"},长度=3,容量=3// 创建一个整型切片// 其长度和容量都是4个元素myNum := []int{10, 20, 30, 40}fmt.Printf("%#v,长度=%d,容量=%d \n", myNum, len(myNum), cap(myNum))// output: []int{10, 20, 30, 40},长度=4,容量=4

当使用切片字面量创建切片时,还可以设置初始长度和容量。要做的就是在初始化时给出所需的长度和容量作为索引。下面的语法展示了如何使用索引方式创建长度和容量都是100个元素的切片:

// 创建字符串切片// 使用空字符串初始化第 100 个元素myStr := []string{99: ""}

通过切片/数组创建切片

切片的本质是底层数组切出的一部分,因此可以通过字符串/数组/指向数组或切片的指针构造新切片。

它有两种变体:一种指定low和high两个索引界限值的简单的形式,另一种是除了low和high索引界限值外,还指定容量的完整的形式。

创建新切片的语法如下:

slice[i:j] (array[i:j])slice[i:j:k] (array[i:j:k])

i: 表示从 slice/array 的第几个元素开始切

j: 控制切片的长度(j-i),一般不包含索引为j的元素。

k: 控制切片的容量(k-i),如果没有给定 k,则表示切到底层数组的最尾部。

下面是几种常见的简写形式:

slice[i:] // 从 i 切到最尾部slice[:j] // 从最开头(0)切到 j(不包含j)slice[:] // 从头切到尾,等价于复制整个slice

让我们通过下面的例子来理解通过切片创建新的切片的本质:

// 通过字面量创建一个整型切片// 其长度和容量都是 5 个元素myNum := []int{10, 20, 30, 40, 50}// 创建一个基于myNum 的新切片// i = 1, j = 3,k未指定,切到最尾部// 长度 = j-i = 2 个元素,容量为 4 个元素newNum := myNum[1:3]fmt.Printf("newNum=%v,长度=%d,容量=%d \n", newNum, len(newNum), cap(newNum))// output: newNum=[20 30],长度=2,容量=4

执行上面的代码后,我们有了两个切片,它们共享同一段底层数组,但通过不同的切片会看到底层数组的不同部分:

注意:截取新切片时的原则是左含右不含。所以 newNum 是从 myNum 的 index=1 处开始截取,截取到 index=3 的前一个元素,也就是不包含 index=3 这个元素。所以,新的 newNum 是由 myNum 中的第2个元素、第3个元素组成,长度为 2,容量为 4。切片 myNum 能够看到底层数组全部 5 个元素的容量,而 newNum 能看到的底层数组的容量只有 4 个元素。newNum 无法访问到底层数组的第一个元素。所以,对 newNum 来说,那个元素就是不存在的。

对切片的操作

为切片添加元素(append())

Go语言的内置函数append()可以为切片动态添加元素。一次可以添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加…)。

var s []int// 添加一个元素s = append(s, 1) // [1]// 添加多个元素s = append(s, 2, 3, 4) // [1 2 3 4]s2 := []int{5, 6, 7}// 添加另一个切片的所有元素s = append(s, s2...) // [1 2 3 4 5 6 7]

注意:通过var声明的零值切片可以在append()函数直接使用,无需初始化。

切片扩容

相对于数组而言,使用切片的一个好处是:可以按需增加切片的容量。Golang 内置的 append() 函数会处理增加长度时的所有操作细节。要使用 append() 函数,需要一个被操作的切片和一个要追加的值,当 append() 函数返回时,会返回一个包含修改结果的新切片。函数 append() 总是会增加新切片的长度,而容量有可能会改变,也可能不会改变,这取决于被操作的切片的可用容量。

例如:

func main() {//append()添加元素和切片扩容var numSlice []intfor i := 0; i < 10; i++ {numSlice = append(numSlice, i)fmt.Printf("%v len:%d cap:%d ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)}}

输出结果:

[0] len:1 cap:1 ptr:0xc000014098[0 1] len:2 cap:2 ptr:0xc0000140e0[0 1 2] len:3 cap:4 ptr:0xc0000121e0[0 1 2 3] len:4 cap:4 ptr:0xc0000121e0[0 1 2 3 4] len:5 cap:8 ptr:0xc00000e340[0 1 2 3 4 5] len:6 cap:8 ptr:0xc00000e340[0 1 2 3 4 5 6] len:7 cap:8 ptr:0xc00000e340[0 1 2 3 4 5 6 7] len:8 cap:8 ptr:0xc00000e340[0 1 2 3 4 5 6 7 8] len:9 cap:16 ptr:0xc000018100[0 1 2 3 4 5 6 7 8 9] len:10 cap:16 ptr:0xc000018100

从上面的结果可以看出:

append()函数将元素追加到切片的最后,并返回该切片。切片numSlice的容量按照1,2,4,8,16这样的规则自动进行扩容,每次扩容后都是扩容前的2倍。

函数 append() 会智能地处理底层数组的容量增长。在切片的容量小于 1024 个元素时,总是会成倍地增加容量。一旦元素个数超过 1024,容量的增长因子会设为 1.25,也就是会每次增加 25%的容量(随着语言的演化,这种增长算法可能会有所改变)。

想要了解切片详细的扩容策略,可以查看$GOROOT/src/runtime/slice.go的源代码。

删除元素

Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。 代码如下:

func main() {// 从切片中删除元素a := []int{30, 31, 32, 33, 34, 35, 36, 37}// 要删除索引为2的元素(32)a = append(a[:2], a[3:]...)fmt.Println(a) //[30 31 33 34 35 36 37]}

总结一下就是:要从切片a中删除索引为index的元素,操作方法是a = append(a[:index], a[index+1:]...)

遍历切片

切片的遍历方式和数组是一致的,支持索引遍历for range遍历

func main() {s := []int{1, 3, 5}// 索引遍历for i := 0; i < len(s); i++ {fmt.Println(i, s[i])}// for range遍历for index, value := range s {fmt.Println(index, value)// value += 1 只能修改副本值,无法修改切片元素内容s[index] += 1 //可以修改切片元素内容}}

需要注意的是,for range创建了每个元素的副本,而不是直接返回对该元素的引用。要想获取每个元素的地址,可以使用切片变量和索引值。

修改切片元素

使用切片变量和索引值修改切片元素的内容。

切片的复制

Golang 内置的 copy() 函数可以将一个切片中的元素拷贝到另一个切片中,其函数声明为:

func copy(dst, src []Type) int

它表示把切片 src 中的元素拷贝到切片 dst 中,返回值为拷贝成功的元素个数。如果 src 比 dst 长,就截断;如果 src 比 dst 短,则只拷贝 src 那部分:

num1 := []int{10, 20, 30}num2 := make([]int, 5)count := copy(num2, num1)fmt.Println(count)fmt.Println(num2)

运行这段单面,输出的结果为:

3[10 20 30 0 0]

3 表示拷贝成功的元素个数。

参考资料

(李文周)Go语言基础之切片

Golang 入门 : 切片(slice)

如果觉得《【Go学习笔记】数据类型之切片(slice)》对你有帮助,请点赞、收藏,并留下你的观点哦!

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