Go切片的用法和结构
Go的切片类型提供了一种方便而有效的方法来处理类型化数据序列。切片类似于其他语言中的数组,但具有一些不寻常的属性。
数组
切片类型是构建在Go数组类型之上的抽象,因此要理解切片,我们必须首先理解数组。
数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。数组类型定义指定长度和元素类型。默认情况下,数组的每个元素都被初始化为元素类型对应的零值,对于数字类型来说就是0。例如,类型[4]int表示由四个整数组成的数组。数组的大小是固定的;其长度是其类型的一部分([4]int和[5]int是不同的、不兼容的类型)。数组可以以通常的方式索引,因此表达式s[n]从零开始访问第n个元素。
Go的数组是值。数组变量表示整个数组;它不是指向第一个数组元素的指针。这意味着,当您分配或传递数组值时,您将复制其内容。(为了避免复制,您可以传递一个指向数组的指针,但那是指向数组的指针,而不是数组。),考虑数组的一种方法是将数组作为一种结构体,但带有索引字段,而不是命名字段:固定大小的复合值。
var a [4]int
a[0] = 1
i := a[0]
// i == 1
// a[2] == 0, the zero value of the int type
b := [2]string{"Penn", "Teller"}
b := [...]string{"Penn", "Teller"} // “...”省略号表示数组的长度是根据初始化值的个数来计算
// 可以直接通过==比较运算符来比较两个数组,只有当两个数组的所有元素都是相等的时候数组才是相等的。不相等比较运算符!=遵循同样的规则。
a := [2]int{1, 2}
b := [...]int{1, 2}
c := [2]int{1, 3}
fmt.Println(a == b, a == c, b == c) // "true false false"
d := [3]int{1, 2}
fmt.Println(a == d) // compile error: cannot compare [2]int == [3]int
数组作为传参
当调用一个函数的时候,函数的每个调用参数将会被赋值给函数内部的参数变量,所以函数参数变量接收的是一个复制的副本,并不是原始调用的变量。因为函数参数传递的机制导致传递大的数组类型将是低效的,并且对数组参数的任何的修改都是发生在复制的数组上,并不能直接修改调用时原始的数组变量。
我们可以显式地传入一个数组指针,函数通过指针对数组的任何修改都可以直接反馈到调用者,示例:
// 给[32]byte类型的数组清零
func zero(ptr *[32]byte) {
for i := range ptr {
ptr[i] = 0
}
}
// 更简洁的写法
func zero(ptr *[32]byte) {
*ptr = [32]byte{}
}
通过指针来传递数组参数是高效的,也允许在函数内部修改数组的值,但是数组使用起来不是很灵活,因为数组是固定长度。上面的zero函数并不能接收指向[16]byte类型数组的指针,而且也没有任何添加或删除数组元素的方法。一般,我们使用slice替代数组。
切片
数组有自己的位置,但它们不灵活,Go代码中你不会经常看到它们。而切片无处不在。它们构建在阵列之上,提供强大的功能和便利性。
切片的类型规范为[]T,其中T是切片元素的类型。与数组类型不同,切片类型没有指定的长度。切片文字与数组文字一样声明,只是忽略了元素计数:
letters := []string{"a", "b", "c", "d"}
// 也可通过内建函数make创建 func make([]T, len, cap) []T
var s []byte
s = make([]byte, 5, 5) // s = []byte{0, 0, 0, 0, 0}
// 当省略容量参数时,它默认为指定的长度,简洁申明
s := make([]byte, 5) // len(5) == 5 cap(s) == 5
切片的零值为零。对于零切片,len和cap函数都将返回0。
切片也可以通过“切片”现有切片或数组来创建。切片是通过指定一个半开放范围来完成的,其中两个索引由冒号分隔。例如,表达式b[1:4]创建一个包含b的元素1到3的切片(结果切片的索引将是0到2)。
b := []byte{'c', 'h', 'i', 'n', 'a'}
// b[1:4] == []byte{'h', 'i', 'n'}
// b[2:] == []byte{'i', 'n', 'a'}
// b[:] == b
切片的结构
切片代表变长的序列,序列中每个元素都有相同的类型。切片是数组段的描述符。它由指向数组的指针、段的长度及其容量(段的最大长度)组成。
s := make([]byte, 5) ,结构如下:
s = s[2:4],结构如下:
动态增长的切片
1、要增加切片的容量,必须创建一个新的、更大的切片,并将原始切片的内容复制到其中。示例通过制作一个新的切片t,将s的内容复制到t,然后将切片值t分配给s,将s的容量翻倍:
s := []byte{'c', 'h', 'i', 'n', 'a'}
t := make([]byte, len(s), (cap(s)+1)*2) // +1 in case cap(s) == 0
for i := range s {
t[i] = s[i]
}
s = t
t := make([]byte, len(s), (cap(s)+1)*2)
copy(t, s)
s = t
// // func copy(dst, src []T) int
func copy1(dist []byte, src []byte) []byte {
for i := range src {
dist[i] = src[i]
}
return dist
}
copy1(s, b)
2、一个常见的操作是将数据附加到切片的末尾。下面示例将字节元素附加到字节切片中,必要时增长切片,并返回更新的切片值:
// func append(s []T, x ...T) []T
func appendByte(slice []byte, data ...byte) []byte {
m := len(slice)
n := m + len(data)
if n > cap(slice) {
newSlice := make([]byte, (n+1)*2)
copy(newSlice, slice)
slice = newSlice
}
slice = slice[0:n]
copy(slice[m:n], data)
return slice
}
p := []byte{2, 3, 5}
p = AppendByte(p, 7, 11, 13)
fmt.Printf("%s", p) // [2 3 5 7 11 13]
// p == []byte{2, 3, 5, 7, 11, 13}
a := []string{"zhangsan", "lisi"}
b := []string{"wangwu", "zhaoliu"}
a = append(a, b...)
fmt.Printf("%q", a) // ["zhangsan" "lisi" "wangwu" "zhaoliu"]