31

GO的第五天,复合数据类型---数组、Slice

 4 years ago
source link: https://studygolang.com/articles/27280
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

数组

数组是一个由 固定长度 的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,因此在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"]

U3EvQ3j.png!web

如果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不直接支持比较运算符呢?这方面有两个原因:

  1. 一个slice的元素是间接引用的,一个slice甚至可以包含自身。
  2. 因为slice的元素是间接引用的,一个固定的slice值(指slice本身的值,不是元素的值)在不同的时刻可能包含不同的元素,因为底层数组的元素可能会被修改。

关于slice更多的信息,下次再详细的写吧。

参考

《GO语言圣经》


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK