Go 语言切片(Slice)
Go 语言切片是对数组的抽象。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
定义切片
你可以声明一个未指定大小的数组来定义切片:
var identifier []type
切片不需要说明长度。
或使用make()函数来创建切片:
var slice1 []type = make([]type, len) 也可以简写为 slice1 := make([]type, len)
也可以指定容量,其中capacity为可选参数。
make([]T, length, capacity)
这里 len 是数组的长度并且也是切片的初始长度。
切片初始化
s :=[] int {1,2,3 }
直接初始化切片,[]表示是切片类型,{1,2,3}初始化值依次是1,2,3.其cap=len=3
s := arr[:]
初始化切片s,是数组arr的引用
s := arr[startIndex:endIndex]
将arr中从下标startIndex到endIndex-1 下的元素创建为一个新的切片
s := arr[startIndex:]
默认 endIndex 时将表示一直到arr的最后一个元素
s := arr[:endIndex]
默认 startIndex 时将表示从arr的第一个元素开始
s1 := s[startIndex:endIndex]
通过切片s初始化切片s1
s :=make([]int,len,cap)
通过内置函数make()初始化切片s,[]int 标识为其元素类型为int的切片
len() 和 cap() 函数
切片是可索引的,并且可以由 len() 方法获取长度。
切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。
以下为具体实例:
实例
import "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]
空(nil)切片
一个切片在未初始化之前默认为 nil,长度为 0,实例如下:
实例
import "fmt"
func main() {
var numbers []int
printSlice(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=[] 切片是空的
切片截取
可以通过设置下限及上限来设置截取切片 [lower-bound:upper-bound],实例如下:
实例
import "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]
append() 和 copy() 函数
如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。
下面的代码描述了从拷贝切片的 copy 方法和向切片追加新元素的 append 方法。
实例
import "fmt"
func main() {
var numbers []int
printSlice(numbers)
/* 允许追加空切片 */
numbers = append(numbers, 0)
printSlice(numbers)
/* 向切片添加一个元素 */
numbers = append(numbers, 1)
printSlice(numbers)
/* 同时添加多个元素 */
numbers = append(numbers, 2,3,4)
printSlice(numbers)
/* 创建切片 numbers1 是之前切片的两倍容量*/
numbers1 := make([]int, len(numbers), (cap(numbers))*2)
/* 拷贝 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=1 cap=1 slice=[0] len=2 cap=2 slice=[0 1] len=5 cap=6 slice=[0 1 2 3 4] len=5 cap=12 slice=[0 1 2 3 4]
老逗先生
104***5037@qq.com
输出结果:
我们可以看出切片,实际的是获取数组的某一部分,len切片<=cap切片<=len数组,切片由三部分组成:指向底层数组的指针、len、cap。
老逗先生
104***5037@qq.com
LeonWilliam
257***8572@qq.com
sliceTest 函数是切片测试代码,简单的两种初始化方式。
twoDimensionsArray 函数是二维数组测试函数。
测试结果:
1.二维数组中的元素(一位数组)个数 > 限制的列数,异常;
2.二维数组中的元素(一位数组)个数 <= 限制的列数,正常;
假设列数为 3, 某个一位数组 {1}, 则后两个元素,默认为 0。
LeonWilliam
257***8572@qq.com
jean
333***2@qq.com
我们基于原数组或者切片创建一个新的切片后,那么新的切片的大小和容量是多少呢?
这里有个公式,对于底层数组容量是 k 的切片 slice[i:j] 来说:
例子:
执行结果:
jean
333***2@qq.com
amano
106***6790@qq.com
结果为:
capacity 为 7 是因为 number3 的 ptr 指向第三个元素, 后面还剩 2,3,4,5,6,7,8, 所以 cap=7。
如:
结果为:
capacity 为 4 是因为 number4 的 ptr 指向第六个元素, 后面还剩 5,6,7,8 所以 cap=4。
amano
106***6790@qq.com
ABC123
abc***c.com
合并多个数组:
输出结果:
ABC123
abc***c.com
lslxdx
lsl***@163.com
在做函数调用时,slice 按引用传递,array 按值传递:
输出结果为:
lslxdx
lsl***@163.com
berakcup
104***3071@qq.com
append() 和 copy() 部分,貌似有没说明白的地方。
当 numbers = [0, 1] 时,append(numbers, 2, 3, 4) 为什么 cap 从 2 变成 6 ?
经过实践得知,append(list, [params]),先判断 list 的 cap 长度是否大于等于 len(list) + len([params]),如果大于那么 cap 不变,否则 cap 等于 max{cap(list), cap[params]},所以当 append(numbers, 2, 3, 4) cap 从 2 变成 6。
berakcup
104***3071@qq.com
andriyy
ano***ous@abc.com
使用 copy 函数要注意对于 copy(dst, src),要初始化 dst 的 size,否则无法复制。
错误示例:
输出结果:
正确示例:
输出结果:
andriyy
ano***ous@abc.com
Noah
lh_***9@126.com
实例:
以上代码执行输出结果为:
Noah
lh_***9@126.com
gibson1112
185***72536@163.com
切片内部结构:
第一个字段表示 array 的指针,是真实数据的指针第二个是表示 slice 的长度,第三个是表示 slice 的容量。
所以 unsafe.Sizeof(切片)永远都是 24。
当把 slice 作为参数,本身传递的是值,但其内容就 byte* array,实际传递的是引用,所以可以在函数内部修改,但如果对 slice 本身做 append,而且导致 slice 进行了扩容,实际扩容的是函数内复制的一份切片,对于函数外面的切片没有变化。
结果如下:
gibson1112
185***72536@163.com
ragonli
rag***ig@gmail.com
slice 的底层是数组指针,所以 slice a 和 s 指向的是同一个底层数组,所以当修改 s[0] 时,a 也会被修改。
ragonli
rag***ig@gmail.com
乱码kun
ly1***657282@gmail.com
当append(list, [params]),先判断 list 的 cap 长度是否大于等于 len(list) + len([params]),如果大于那么 cap 不变,否则 cap = 2 * max{cap(list), cap[params]},所以当 append(numbers, 2, 3, 4) cap 从 2 变成 6。
前面已有相关详解,很可惜关键部分把“2 * ”给漏了,在此补充。
乱码kun
ly1***657282@gmail.com
Aaron_kitty
235***8645@qq.com
关于cap为何变为6的问题,我认为以上解释均为错,你们可以试试添加7个元素
cap就是10.
cap*2小于len(list) + len([params])则cap = len(list) + len([params])+1,反之cap=cap*2
查阅官方文档说
切片一旦初始化,就总是伴随着一个包含其元素的基本数组。 因此,切片与其数组及其它本数组的切片共享存储; 与此相反,不同的数组总是表示其不同的存储。
切片的基本数组可扩展其切片的结尾。容量是该扩展的量度: 它是切片的长度和切片往后数组的长度之和;长度达到其容量的切片可通过从原切片 (§Slices)‘切下’一个新的来创建。 切片 a 的容量可使用内建函数 cap(a) 获取。
并未指出规律性,以上规律是我个人总结。
Aaron_kitty
235***8645@qq.com
疏林如有诉
191***4098@qq.com
我觉得前面关于 cap 为什么会变成6的问题的解答都不太正确。
当添加 6 个元素时按照上面的说法 cap 的值应该分别为 12,9;但是经过实践得到的 cap 值为 8,可见以上几种说法都不是很全面。
经过多个数据的实验我发现 cap 的值只能为偶数。
实验数据如下:
当起始cap为2时,添加2个元素时cap为4,添加3个元素时cap为6,添加4个元素时cap为6,添加5个元素时cap为8,添加6个元素时cap为8,添加7个元素时cap为10,添加8个元素时cap为10……
当起始cap为1时,添加2个元素时cap为4,添加3个元素时cap为4,添加4个元素时cap为6,添加5个元素时cap为6,添加6个元素时cap为8,添加7个元素时cap为8,添加8个元素时cap为10……
所以我总结出来的经验是,添加元素后当len[list]+len[params]>cap时;cap的值需要修改。
若len[list]+len[params]为偶数,则cap=len[list]+len[params]
若len[list]+len[params]为奇数,则cap=len[list]+len[params]+1
以上为个人总结经验。
疏林如有诉
191***4098@qq.com
Raindeavor
wyw***ele@163.com
关于 cap 为何变为 6 的问题,我认为楼上解释也不对,哈哈。
1、当同时添加多个元素时:
即 cap 始终为偶数。
2、当一个一个添加元素时:
即 cap 总是呈 2 倍的增加(也是偶数)。
以上规律是我观察输出后的个人总结,初学 Go,没查文档什么的,总结的有问题还请楼下指出,嘿嘿。
Raindeavor
wyw***ele@163.com
Eternity味道
271***298@qq.com
关于切片容量 cap,当切片容量不够时,cap 会自动扩容到 2 倍:
当一个一个元素追加到切片中,切片容量变化过程为:
Eternity味道
271***298@qq.com
go语言初学者
Bao***_snow@163.com
关于楼上所讨论的 cap 由 2-> 6,根据查阅文档,可以得出一个结论:通过 append() 函数向数组中添加元素,首先 cap 会以二倍的速度增长,如果发现增长 2 倍以上的容量可以满足扩容后的需求,那么 cap*2,否则就会看扩容后数组的 length 是多少 cap=length+1。
go语言初学者
Bao***_snow@163.com
bullet
jia***angyu@163.com
参考地址
以 s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 为例:
建议:做 slice 截取时建议用两个参数,尤其是从底层数组进行切片操作时,因为这样在进行第一次 append 操作时,会给切片重新分配空间,这样减少切片对数组的影响。
结论:s = s[low : high : max] 切片的三个参数的切片截取的意义为 low 为截取的起始下标(含), high 为窃取的结束下标(不含 high),max 为切片保留的原切片的最大下标(不含 max);即新切片从老切片的 low 下标元素开始,len = high - low, cap = max - low;high 和 max 一旦超出在老切片中越界,就会发生 runtime err,slice out of range。另外如果省略第三个参数的时候,第三个参数默认和第二个参数相同,即 len = cap。
运行结果:
修改 max 值,发生越界错误:
执行后,错误信息如下:
bullet
jia***angyu@163.com
参考地址
da蘑菇大
860***289@qq.com
append 会让切片和与他相关的切片脱钩,但地址不变:
输出:
da蘑菇大
860***289@qq.com
网友小王
770***908@qq.com
为什么这里的 cap 不是 5 呢?
要弄清楚这个问题,先要理解如下代码:
以上代码执行输出结果为:
解释:每次cap改变的时候指向array内存的指针都在变化。当在使用 append 的时候,如果 cap==len 了这个时候就会新开辟一块更大内存,然后把之前的数据复制过去(实际go在append的时候放大cap是有规律的。在 cap 小于1024的情况下是每次扩大到 2 * cap ,当大于1024之后就每次扩大到 1.25 * cap 。所以上面的测试中cap变化是 1, 2, 4, 8)。
网友小王
770***908@qq.com