「这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战」。
切片
slice
的底层数据是数组,slice
是对数组的封装,它描述一个数组的片段。两者都可以通过下标来访问单个元素。数组是定长的,长度定义好之后,不能再更改。而切片则非常灵活,它可以动态地扩容。切片的类型和长度无关。数组就是一片连续的内存,slice
实际上是一个结构体,包含三个字段:长度、容量、底层数组。切片的数据结构如下图:
1 | go复制代码// runtime/slice.go |
1 概述
- 切片是围绕动态数组概念构建的。但是切片本身并不是动态数组,而是对底层数组的抽象。
- 按需自动改变大小
- 与数组相比,切片的长度可以在运行时修改
- 切片不存具体的值,切片是一个引用类型
- 相当于java中的ArrayList
2 引子
因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性。 例如:
1 | go复制代码func arraySum(x [3]int) int{ |
这个求和函数只能接受[3]int类型,其他的都不支持。 再比如,
a := [3]int{1, 2, 3}
数组a中已经有三个元素了,我们不能再继续往数组a中添加新元素了。
3 切片的定义
::: tip 声明切片类型的基本语法
var name []T
:::
name:表示变量名 T:表示切片中的元素类型,举例如下:
1 | go复制代码func main() { |
::: tip 切片的长度和容量
切片拥有自己的长度和容量,我们可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量。
:::
4 基于数组定义切片
由于切片的底层就是一个数组,所以我们可以基于数组定义切片。
1 | go复制代码func main() { |
还支持如下方式:
1 | go复制代码c := a[1:] //[56 57 58 59] |
5 切片再切片
除了基于数组得到切片,我们还可以通过切片来得到切片。
1 | go复制代码func main() { |
输出
1 | go复制代码a:[北京 上海 广州 深圳 成都 重庆] type:[6]string len:6 cap:6 |
::: warning 注意
对切片进行再切片时,索引不能超过原数组的长度,否则会出现索引越界的错误。
:::
6 使用make()构造切片
我们上面都是基于数组来创建的切片,如果需要动态的创建一个切片,我们就需要使用内置的make()函数,
::: tip 格式如下
make([]T, size, cap)
:::
- T:切片的元素类型
- size:切片中元素的数量
- cap:切片的容量
举例如下
1 | go复制代码func main() { |
上面代码中a的内部存储空间已经分配了10个,但实际上只用了2个。 容量并不会影响当前元素的个数,所以len(a)返回2,cap(a)则返回该切片的容量。
make初始化和普通声明切片的区别
1 | go复制代码 //用make初始化切片,并赋值初始值 |
7 切片的本质
::: tip 切片本质
切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。
举个例子,现在有一个数组a := [8]int{0, 1, 2, 3, 4, 5, 6, 7},切片s1 := a[:5],相应示意图如下。
:::
切片s2 := a[3:6],相应示意图如下:
切片不能直接比较
切片之间是不能比较的,我们不能使用==操作符来判断两个切片是否含有全部相等元素。 切片唯一合法的比较操作是和nil
比较。 一个nil值的切片并没有底层数组,一个nil
值的切片的长度和容量都是0。但是我们不能说一个长度和容量都是0的切片一定是nil
,例如下面的示例:
1 | go复制代码var s1 []int //len(s1)=0;cap(s1)=0;s1==nil |
::: warning 注意
所以要判断一个切片是否是空的,要是用len(s) == 0来判断,不应该使用s == nil来判断。
:::
8 切片的赋值拷贝
下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。
8.1 一般赋值
1 | go复制代码func main() { |
8.2 使用copy()函数复制切片
1 | go复制代码func main() { |
由于切片是引用类型,所以a和b其实都指向了同一块内存地址.修改b的同时a的值也会变化.
Go语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中,copy()函数的使用格式如下:
::: tip 格式如下
copy(destSlice, srcSlice []T)
- srcSlice: 数据来源切片
- destSlice: 目标切片
:::
1 | go复制代码func main() { |
9 切片遍历
切片的遍历方式和数组是一致的,支持索引遍历和for range遍历类似于java的增强foreach。
1 | go复制代码func main() { |
10 append()方法为切片添加元素
Go语言的内建函数
append()
可以为切片动态添加元素。 每个切片会指向一个底层数组,这个数组能容纳一定数量的元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”,此时该切片指向的底层数组就会更换。“扩容”操作往往发生在append()
函数调用时。 举个例子
1 | go复制代码func main() { |
输出
1 | go复制代码[0] len:1 cap:1 ptr:0xc0000a8000 |
一次性追加多个元素
1 | go复制代码var citySlice []string |
11 从切片中删除元素
Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。 代码如下:
1 | go复制代码func main() { |
12 切片删除元素再举例
1 | go复制代码 arr := [...]int{1, 3, 5, 7, 9, 11} |
- 切片永远是不存值的(切片是指向对应一个底层数组,底层数组都是占用一块连续的内存)
- 要从切片a中删除索引为index的元素,操作方法是a = append(a[:index], a[index+1:]…)
:::
::: danger 切片的内置函数汇总
- len 获取slice的长度
- cap 获取slice的最大容量
- append 向slice里面追加一个或者多个元素,然后返回一个和slice一样类型的slice
- copy 函数copy从源slice的src中复制元素到目标dst,并且返回复制的元素的个数
:::
本文转载自: 掘金