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

Mr_J · · 608 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

数组

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

如果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语言圣经》


有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:Mr_J

查看原文:GO的第五天,复合数据类型---数组、Slice

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

608 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传