学到什么
- 什么是切片?
- 如何创建切片?
- 如何获取切片长度和容量?
- 切片和数组的关系?
- 操作切片具体元素?
- 切片元素如何追加和移除?
- 切片是引用类型还是值类型?
- 如何拷贝切片?
- 如何创建多维切片?
- 切片字符串是啥?
概念
在学习切片之前请先将上篇文章《内置集合 - 数组》搞明白。
切片使用起来类似长度可变的数组,不像数组长度是固定的。但切片的底层使用的还是数组,切片只是保存了对数组的引用,帮着管理数组,实现可变的效果。
声明
格式:
var 切片名称 []数据类型
和数组声明的区别是,是否指明了长度,没有长度则为切片。
var nums []int
注:切片未初始化默认为 nil ,长度为 0 。如果清空切片可以赋值 nil,例:
nums = nil
。初始化
1. make 函数
使用 make 函数初始化切片,容量(马上有讲)参数可以省略,省略后长度和容量相等,格式如下:
切片名称 := make([]数据类型,长度,容量)
示例:
// 长度为 2,容量为 5 nums := make([]int, 2, 5)
下图是示例代码的原理图,下来深层了解切片。
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fd54b0f25-9426-4b83-9c51-2e6165a030b2%2Fce16cf20-7293-48fa-b1e7-548c7c4bbc95%2FUntitled.png%3Ftable%3Dblock%26id%3D6d4d710a-1ac2-4d6f-a585-c9ae63163641%26spaceId%3Dd54b0f25-9426-4b83-9c51-2e6165a030b2%26expirationTimestamp%3D1725732000000%26signature%3DQPmaSt9klf4187xdud_seAEr0vR-43JGRQ76ACknG2k?table=block&id=6d4d710a-1ac2-4d6f-a585-c9ae63163641&cache=v2)
蓝色区域为切片的结构,它包含数组的指针(ptr)、切片长度(len)和切片容量(cap)。
- ptr:数组指针,保存数组的内存地址,指向数组的具体索引。
- len:切片的长度,可以使用
len(nums)
函数获取,表示从指针对应的索引位置开始所使用的长度。
- cap:切片的容量,可以使用
cap(nums)
函数获取,表示引用数组的长度。
黄色区域为切片底层引用的数组,数组的长度就是切片的容量。
2. 初始化具体值
nums := []int{1, 2, 3}
初始化了一个长度为 3 的切片,此时容量也为3。
操作具体元素
切片中元素的具体操作和数组的方式是一样的。如果获取元素时超出切片长度,即使没有超出容量,编译器也会报错。
nums := []int{1, 2, 3} // 设置索引 1 的元素为 4 nums[1] = 4 fmt.Println(nums[1]) // 输出 4
获取子集
定义了一个切片或数组后,可以获取其中的一部分,即子集。
格式:
切片或数组[开始索引:结束索引]
获取从“开始索引”到“结束索引”的子集,包含开始索引,但不包含结束索引。如果是数组获取子集后,类型会转化为切片类型。
// 切片 nums := []int{1, 2, 3, 4, 5} // 获取切片子集 nums1 := nums[2:4] // []int{3, 4} // 数组 arr := [5]int{1, 2, 3, 4, 5} // nums2 为切片类型 nums2 := arr[2:4] // []int{3, 4}
省略索引
“开始索引”和“结束索引”都可以省略。
- 开始索引省略,表示子集从索引 0 开始到结束索引。
- 结束索引省略,表示子集从开始索引到最后结束。
- 都省略,如果是切片两者一样,如果是数组会转化为切片类型。
nums := []int{1, 2, 3, 4, 5} // 开始索引省略 nums1 := nums[:3] // []int{1, 2, 3} // 结束索引省略 nums2 := nums[2:] // []int{3, 4, 5} // nums3 和 nums 相同 // 等价于:nums3 := nums nums3 := nums[:]
如果代码中
nums
为数组,那 nums[:]
会将数组转化为切片。下来看看以上代码的原理图,这样会加深理解。![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fd54b0f25-9426-4b83-9c51-2e6165a030b2%2F47eb58c9-6fd6-4b62-ab5b-3dc44af451fa%2FUntitled.png%3Ftable%3Dblock%26id%3Df7d43527-27db-4455-a7fa-2bd24e69277f%26spaceId%3Dd54b0f25-9426-4b83-9c51-2e6165a030b2%26expirationTimestamp%3D1725732000000%26signature%3DCTOSV-_PyXe7Nk0z7_aC3j799GnSH9hv8ayA_QfOHsE?table=block&id=f7d43527-27db-4455-a7fa-2bd24e69277f&cache=v2)
从图中可以看出所有的切片都指向同一个数组,这也说明了切片是一个引用类型,它在传递时不会进行拷贝。
追加和移除元素
往切片中追加元素,使用到
append
函数,此函数只能追加到切片末尾。// collection/slice-append.go nums := []int{1, 2, 3} nums = append(nums, 2) nums = append(nums, 4, 5) fmt.Println(nums) // 输出 [1 2 3 2 4 5]
如果想追加到切片开头,没有原生的函数,使用
append
变向的实现,这种方式其实就是合并两个切片。// collection/slice-append-front.go nums := []int{1, 2, 3} // ... 三个点表示将切片元素展开传递给函数 nums = append([]int{4}, nums...) fmt.Println(nums) // 输出 [4 1 2 3]
如何移除某个元素呢,使用切片子集和
append
函数变向实现。// collection/slice-append-remove.go nums := []int{1, 2, 3, 4, 5} // 移除索引为 2 的元素值 3 nums = append(nums[:2], nums[3:]...) fmt.Println(nums) // 输出 [1 2 4 5]
切片拷贝
1. copy 函数
从上面了解到切片是一个引用类型,因此不能像数组一样直接赋值给一个新变量就会产生拷贝。下来使用
copy
函数完成切片的拷贝。// collection/slice-copy-1.go // 将 nums 拷贝到 numsCopy nums := []int{1, 2, 3} numsCopy := make([]int, 3) copy(numsCopy, nums) // 修改了 numsCopy,不会对 nums 产生影响 numsCopy[0] = 2 fmt.Println("nums:", nums) fmt.Println("numsCopy:", numsCopy) // 输出 nums: [1 2 3] numsCopy: [2 2 3]
numsCopy
长度可以小于或大于 nums
的长度,如果小于就会拷贝nums
前面一部分,大于会保留 numsCopy
后面一部分。// collection/slice-copy-2.go // numsCopy 长度小于 nums nums := []int{1, 2, 3} numsCopy := make([]int, 2) // 前面两个元素 1 和 2 被复制 copy(numsCopy, nums) fmt.Println("numsCopy(小于):", numsCopy) // numsCopy 长度大于 nums numsCopy = []int{4, 5, 6, 7} // 4,5,6 会被覆盖,保留 7 copy(numsCopy, nums) fmt.Println("numsCopy(大于):", numsCopy) // 输出 numsCopy(小于): [1 2] numsCopy(大于): [1 2 3 7]
2. “长度 > 容量”触发拷贝
使用
append
函数给切片追加元素时,如果追加的长度大于切片的容量时,切片的底层数组空间则重新开辟一块比原来大的地方,并把原来的数组值拷贝一份。![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fd54b0f25-9426-4b83-9c51-2e6165a030b2%2Faacb1b25-92e0-4b86-9650-4177bb74247d%2FUntitled.png%3Ftable%3Dblock%26id%3Dd8407b76-7e33-426e-9e53-7d8509e40a0a%26spaceId%3Dd54b0f25-9426-4b83-9c51-2e6165a030b2%26expirationTimestamp%3D1725732000000%26signature%3DaPIvpk8RXrU7glT8w8xSUQYJlFAH0UvkSHtyciUN25U?table=block&id=d8407b76-7e33-426e-9e53-7d8509e40a0a&cache=v2)
注解:
- 图中”新数组“两个位置就是切片长度大于容量的时刻,这两个时刻会自动开辟新数组,与原来的数组没有任何关联,只是把值拷贝了一份。
- 图中创建”新数组“时,容量的大小是原来的 2 倍,但这不是一成不变的,不同情况算法也会不一样,想要了解清楚我推荐一篇文章《深度解密Go语言之Slice》。
多维切片
这块和多维数组是类似的,唯一的不同点是切片没有指明长度,举个例子:
// 声明二维切片 var mult [][]int // 初始化二维切片 students := [][]int{ {2, 2, 0}, {2, 2, 2}, {2, 1, 2}, {2, 2, 2}, }
注:如果想创建三维切片、四维切片,只要和多维数组类比就行。
切片字符串
这个是啥呢?是字符串可以使用上面的子集用法,来获取字符串中的一部分。
str := "I'm laomiao." fmt.Println(str[4:7]) // 输出 lao
总结
本篇围绕”切片“进行了重点讲解,在实际开发中也常常被使用,所以一定要掌握清楚。文中没有写明如何遍历切片,是因为它和数组的使用是一样的,如果不懂,请翻阅上篇文章。