一尘不染

为什么Go中有数组?

go

我了解Go中数组和切片之间的区别。但是我不明白的是为什么完全拥有数组会有所帮助。数组类型定义指定长度和元素类型为什么有帮助?为什么我们使用的每个“数组”都不能切片?


阅读 293

收藏
2020-07-02

共1个答案

一尘不染

数组不仅仅是固定长度,还有更多:它们是
可比较的
,并且它们是
(不是引用或指针类型)。

在某些情况下,数组比切片有无数的优势,所有这些优点加在一起证明了数组的存在(以及切片)。让我们看看他们。(我什至不算数组是切片的基础。)


1. 具有可比性意味着 您可以将数组用作地图中的键 ,但不能 用作
切片。是的,您现在可以说为什么不让切片具有可比性,因此仅凭这一点并不能证明两者均存在。切片上没有很好地定义平等。常见问题解答:为什么地图不允许切片作为键?

他们没有实现平等,因为在此类类型上对平等的定义不明确。有多种考虑因素,包括浅层比较与深层比较,指针与值比较,如何处理递归类型等等。

2. 数组还可以为您提供更高的编译时安全性
,因为可以在编译时检查索引范围(数组长度必须计算为一个可以由type值表示的非负常量int):

s := make([]int, 3)
s[3] = 3 // "Only" a runtime panic: runtime error: index out of range

a := [3]int{}
a[3] = 3 // Compile-time error: invalid array index 3 (out of bounds for 3-element array)

3. 同样, 传递或分配数组值也将隐式创建 整个数组 的副本 ,因此它将与原始值“分离”。如果您传递一个切片,它将仍然仅复制切片
标头
,但切片值(标头)将指向相同的后备数组。这可能是您想要的,也可能不是。如果要从“原始”切片“分离”切片,则必须显式复制内容,例如使用内置copy()函数复制到新切片。

a := [2]int{1, 2}
b := a
b[0] = 10 // This only affects b, a will remain {1, 2}

sa := []int{1, 2}
sb := sa
sb[0] = 10 // Affects both sb and sa

4. 同样,由于数组长度是数组类型的一部分,所以 长度不同的数组是不同的类型
。一方面,这可能是“痛苦中的事情”(例如,编写一个带有类型参数的函数[4]int,您不能使用该函数来处理和处理类型数组[5]int),但这也可能是一个优势:可以用于
显式指定 所需数组 的长度
。例如,您想编写一个使用IPv4地址的函数,可以使用type对其进行建模[4]byte。现在,您有了编译时的保证,即传递给您的函数的值将恰好具有4个字节,不多也不少(无论如何这都是无效的IPv4地址)。

5. 与之前的内容有关, 数组长度也可以用于记录目的
。类型[4]byte正确记录了IPv4有4个字节。一个rgb类型的可变[3]byte告诉有对每个颜色成分1个字节。在某些情况下,甚至可以将其取出并单独记录;例如在crypto/md5包中:md5.Sum()返回类型为的值,[Size]byte其中md5.Size一个常数为16:MD5校验和的长度。

6.计划结构类型的内存布局
时,它们也非常有用,请参阅此处的JimB答案,以及更多详细信息和实际示例

7. 同样,由于切片是标头,并且它们(几乎)总是按原样传递(没有指针), 因此语言规范对切片的指针比对数组的指针更具限制性
。例如,规范提供了多个用于操作数组指针的简写,而在切片时会给出编译时错误(因为很少使用指向切片的指针,如果仍然需要/必须这样做,则必须明确处理;)

这样的例子是:

  • 分割p指向array:的指针p[low:high]是的简写(*p)[low:high]。如果p是指向slice的指针,则为编译时错误(规范:slice表达式)。

  • 索引p数组指针:p[i]是的简写(*p)[i]。如果pis是指向切片的指针,则这是编译时错误(规范:Index expressions)。

例:

pa := &[2]int{1, 2}
fmt.Println(pa[1:1]) // OK
fmt.Println(pa[1])   // OK

ps := &[]int{3, 4}
println(ps[1:1]) // Error: cannot slice ps (type *[]int)
println(ps[1])   // Error: invalid operation: ps[1] (type *[]int does not support indexing)

8. 访问(单个)数组元素 比访问切片元素 更有效 ;像分片一样,运行时必须经过隐式指针取消引用。另外, “如果表达式
的类型是数组或指向数组的指针,则表达式len(s)cap(s)为常量s

可能令人惊讶,但您甚至可以写:

type IP [4]byte

const x = len(IP{}) // x will be 4

它是有效的,即使IP{}不是常量表达式const i = IP{}也要进行评估和编译时,因此例如将是编译时错误!在此之后,以下操作也就不足为奇了:

const x2 = len((*IP)(nil)) // x2 will also be 4

注意:在一个完整的数组与一个完整的切片之间进行测量时,可能根本没有性能上的差异,因为显然可以对其进行优化,以使切片头中的指针仅被取消引用一次。有关详细信息/示例,请参阅数组与切片:访问速度


必须阅读的博客:

切成薄片:用法和内部原理

数组,切片(和字符串):“追加”的机制

2020-07-02