数组
数组是一个由 固定长度
的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,因此在Go语言中很少直接使用数组。和数组对应的类型是Slice(切片),它是可以增长和收缩动态序列。
默认情况下,数组的每个元素都被初始化为元素类型对应的零值,对于数字类型来说就是0。
var a [3]int
var q [3]int = [3]int{1,2,3}
var r [3]int = [3]int{1,2}
fmt.Println(r[2]) // 0
在数组字面值中,如果在数组的 长度位置
出现的是“...”省略号,则表示数组的长度是根据 初始化值的个数
来计算。因此,上面q数组的定义可以简化为:
var q := [...]int{1,2,3}
fmt.Printf("%T\n", q) // [3]int
数组的 长度
是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型。数组的长度必须是 常量表达式
,因为数组的长度需要在 编译阶段
确定。
q := [3]int{1,2,3}
q = [3]int{1,2,3,4} // cannot use [4]int literal (type [4]int) as type [3]int in assignment
定义了一个含有100个元素的数组r,最后一个元素被初始化为-1,其它元素都是用0初始化:
r := [...]int{99:-1}
如果一个数组的元素类型是可以相互比较的,那么数组类型也是可以相互比较的,这时候我们可以直接通过==比较运算符来比较两个数组,只有当两个数组的所有元素都是相等的时候数组才是相等的。不相等比较运算符!=遵循同样的规则。
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
}
}
虽然通过指针来传递数组参数是高效的,而且也允许在函数内部修改数组的值,但是数组依然是 僵化的类型
,因为数组的类型包含了僵化的长度信息。上面的zero函数并不能接收指向[16]byte类型数组的指针,而且也没有任何添加或删除数组元素的方法。由于这些原因,除了像SHA256这类需要处理特定大小数组的特例外,数组依然很少用作函数参数
;相反,我们一般使用slice来替代数组。
Slice
Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是 没有固定长度
而已。
数组和slice之间有着紧密的联系。一个slice是一个 轻量级的数据结构
,提供了访问数组子序列(或者全部)元素的功能,而且slice的底层确实 引用一个数组对象
。一个slice由三个部分构成:指针、长度和容量
。指针指向第一个slice元素对应的底层数组元素的地址,要注意的是slice的第一个元素并不一定就是数组的第一个元素。长度对应slice中元素的数目
;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置
。内置的len和cap函数分别返回slice的长度和容量。
slice的切片操作s[i:j],其中0 ≤ i≤ j≤ cap(s),用于创建一个新的slice,引用s的从第i个元素开始到第j-1个元素的子序列。新的slice将只有j-i个元素。如果i位置的索引被省略的话将使用0代替,如果j位置的索引被省略的话将使用len(s)代替。
下面代码和图片更直观的解释了slice:
months := [...]string{1: "January", /* ... */, 12: "December"}
Q2 := months[4:7]
summer := months[6:9]
fmt.PrintIn(Q2) // ["April" "May" "June"]
fmt.PrintIn(summer) // ["June" "July" "August"]
如果slice超出了容量cap(s)的上限将导致一个panic异常,但是超出len(s)则是意味着扩展了slice,因为新slice的长度会变大:
fmt.Println(summer[:20]) // panic: out of range
endlessSummer := summer[:5] // extend a slice (within capacity)
fmt.Println(endlessSummer) // "[June July August September October]"
和数组不同的是,slice之间不能比较
,因此我们不能使用==操作符来判断两个slice是否含有全部相等元素。不过标准库提供了高度优化的bytes.Equal函数来判断两个字节型slice是否相等([]byte),但是对于其他类型的slice,我们必须自己展开每个元素进行比较:
func equal(x, y []string) bool {
if len(x) != len(y) {
return false
}
for i := range x {
if x[i] != y[i] {
return false
}
}
return true
}
为何slice不直接支持比较运算符呢?这方面有两个原因:
- 一个slice的元素是间接引用的,一个slice甚至可以包含自身。
- 因为slice的元素是间接引用的,一个固定的slice值(指slice本身的值,不是元素的值)在不同的时刻可能包含不同的元素,因为底层数组的元素可能会被修改。
关于slice更多的信息,下次再详细的写吧。
参考
《GO语言圣经》
有疑问加站长微信联系(非本文作者)