【问题标题】:Why have arrays in Go?为什么在 Go 中有数组?
【发布时间】:2016-12-03 08:46:40
【问题描述】:

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

【问题讨论】:

标签: arrays go slice


【解决方案1】:

arrays 不仅仅是固定长度:它们是 comparable,它们是(不是引用或指针类型)。

在某些情况下,数组比切片有无数的优势,所有这些都足以证明数组(以及切片)的存在是合理的。让我们看看他们。 (我什至没有将数组算作切片的构建块。)


1.具有可比性意味着您可以将数组用作地图中的键,但不能使用切片。是的,你现在可以说为什么不让切片具有可比性,这样就不能证明两者的存在是合理的。在切片上没有很好地定义平等。 FAQ: Why don't maps allow slices as keys?

它们没有实现相等,因为在此类类型上没有很好地定义相等;有多种考虑,涉及浅层与深层比较、指针与值比较、如何处理递归类型等等。

2. 数组还可以为您提供更高的编译时安全性,因为可以在编译时检查索引边界(数组长度必须计算为非负constant 可以用 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. 此外,传递或分配数组值将隐式复制整个数组,因此它将与原始值“分离”。如果你传递一个切片,它仍然会复制切片header,但切片值(标题)将指向同一个后备数组。这可能是也可能不是您想要的。如果要从“原始”切片中“分离”切片,则必须显式复制内容,例如使用内置的 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 地址的函数,可以使用 [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 在此处的回答和this answer in greater detail and real-life example

7. 此外,由于切片是标题,并且它们(几乎)总是按原样(没有指针)传递,语言规范对切片指针的限制比指向的指针更严格数组。例如,规范提供了多个使用指向数组的指针进行操作的简写,而在切片的情况下同样会产生编译时错误(因为很少使用指向切片的指针,如果你仍然想要/必须这样做,你必须明确处理它;在this answer中阅读更多内容。

这样的例子是:

  • 分割p 指向数组的指针:p[low:high](*p)[low:high] 的简写。如果p 是指向切片的指针,则这是编译时错误(spec: Slice expressions)。

  • 索引p 指向数组的指针:p[i](*p)[i] 的简写。如果p 是指向切片的指针,则这是编译时错误(spec: 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. 访问(单个)数组元素比访问切片元素更有效;在切片的情况下,运行时必须通过隐式指针取消引用。另外“如果s 的类型是数组或指向数组的指针,则表达式len(s)cap(s) 是常量”

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

type IP [4]byte

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

它是有效的,即使IP{} 不是constant expression,也会被评估和编译,例如const i = IP{} 将是编译时错误!在此之后,以下内容也有效也就不足为奇了:

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

注意:在完整数组与完整切片上进行测距时,可能根本没有性能差异,因为显然可以对其进行优化,以便切片头中的指针仅被取消引用一次。有关详细信息/示例,请参阅Array vs Slice: accessing speed


查看可以使用数组/比切片更有意义的相关问题:

Why use arrays instead of slices?

Why can't Go slice be used as keys in Go maps pretty much the same way arrays can be used as keys?

Hash with key as an array type

How do I check the equality of three values elegantly?

Slicing a slice pointer passed as argument

这只是出于好奇: a slice can contain itself while an array can't。 (实际上这个属性使比较更容易,因为您不必处理递归数据结构)。

必读博客:

Go Slices: usage and internals

Arrays, slices (and strings): The mechanics of 'append'

【讨论】:

  • 恕我直言,除了68 之外,这些都不是“基本”优势(即不是特定于 Go 中糟糕的设计选择)。我相信 Go 设计者只是想为更精确的内存管理留下一个可能性,因此我们有数组。
【解决方案2】:

数组是值,用值代替指针通常很有用。

可以比较值,因此您可以使用数组作为映射键。

值总是被初始化的,所以你不需要初始化,或者make它们就像你对切片做的那样。

数组可以让您更好地控制内存布局,因为您不能直接在带有切片的结构中分配空间,您可以使用数组:

type Foo struct {
    buf [64]byte
}

这里,Foo 值将包含一个 64 字节的值,而不是需要单独初始化的切片头。数组还用于填充结构以在与 C 代码互操作时匹配对齐,并防止错误共享以获得更好的缓存性能。

提高性能的另一个方面是,您可以比使用切片更好地定义内存布局,因为数据局部性会对内存密集型计算产生非常大的影响。与对数据执行的操作相比,取消引用指针可能需要相当长的时间,并且复制小于缓存行的值会产生非常小的成本,因此性能关键代码通常仅出于这个原因使用数组。

【讨论】:

    【解决方案3】:

    数组在节省空间方面更有效。如果您从不更新切片的大小(即从预定义的大小开始并且从不超过它),那么性能差异确实不大。但是在空间上有额外的开销,因为切片只是一个包装器,其核心包含数组。从上下文来看,它还提高了清晰度,因为它使变量的预期用途更加明显。

    【讨论】:

    • 实际上可能存在非常大的性能差异。使用指针(通过切片)可能会影响数据局部性并导致在内存中查找数据的额外成本。
    【解决方案4】:

    每个数组都可以是一个切片,但不是每个切片都可以是一个数组。如果你有一个固定的集合大小,你可以从使用数组中获得轻微的性能改进。至少可以节省切片头占用的空间。

    【讨论】:

      猜你喜欢
      • 2011-05-12
      • 2011-04-24
      • 1970-01-01
      • 2014-08-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多