【发布时间】:2016-10-20 10:05:57
【问题描述】:
为什么 Go 切片(它是 Go 数组的一种实现)不能用作 Go 映射中的键,就像数组可以用作键一样?
【问题讨论】:
-
可以比较切片是否相等吗?如果否,则不能使用它,或者更确切地说,任何没有相等比较的类型都不能用作映射中的键。
标签: arrays dictionary go slice
为什么 Go 切片(它是 Go 数组的一种实现)不能用作 Go 映射中的键,就像数组可以用作键一样?
【问题讨论】:
标签: arrays dictionary go slice
这是来自https://groups.google.com/forum/#!topic/golang-nuts/zYlx6sR4F8Y的Nigel Tao的回答:
一个原因是数组是值类型。如果
a0是[N]int(一个 数组)然后做a1 := a0 a1[0] = 0完全不会影响
a0[0]。相比之下,切片指的是底层数组。复制切片 值是 O(1) 而不是 O(length)。如果
s0是[]int(切片) 然后做s1 := s0 s1[0] = 0会影响
s0[0]是什么。http://play.golang.org/p/TVkntIsLo8
映射键需要一些相等的概念。对于数组,这很简单 元素平等。对于切片,有不止一种方法可以 定义相等:一个是元素相等,另一个是指 相同的数组后备存储。此外,地图插入是否需要 制作整个后备阵列的(昂贵的)副本?复制会 可能不那么令人惊讶,但它与什么不一致 分配确实。
这段代码 sn-p 应该打印什么?
m := make(map[[]int]bool) s0 := []int{6, 7, 8} s1 := []int{6, 7, 8} s2 := s0 m[s0] = true s2[0] = 9 println(m[s0]) println(m[s1]) println(m[s2])不同的程序员可能有不同的期望。避免 混淆,我们只是决定不允许切片作为映射键 现在。
【讨论】:
回答确切的“为什么不能?”:
comparison operators == 和 != 必须为键类型的操作数完全定义;因此键类型不能是函数、映射或切片。
规范不允许未定义比较的键类型。 Spec: Comparison operators 也证实了这一点:
切片、地图和函数值不可比较。
有关推理,请参阅 smarx 的答案(引用 Nigel Tao's answer)。继续阅读。
Go 的地图使用hashmap 实现。通常(无论编程语言如何)对用作哈希映射中的键的值进行变异可能会导致未定义的(或最少的意外)行为。通常用key的hashcode来指定放置值(key-value对)的桶。如果键更改并且您要求该键的关联值,则实现可能会在错误的存储桶中查找(因此报告它找不到它),因为更改的键值很可能会给出不同的哈希码,这可能会指定一个不同的桶。
在 Go 中,切片只是底层数组连续部分的描述符,分配切片值只会复制这些描述符。因此,使用切片作为键,您会期望映射实现仅复制此切片标头(这是指向底层数组中第一个引用元素的指针,长度和容量)。它只有在哈希计算和相等性使用这三个元素时才有效,但对我们(人类,程序员)来说,切片意味着可以通过切片头访问的元素 - 也可以修改(导致上述问题) )。
如果地图允许切片作为键,为了正常运行,它必须在任何切片(用作键)的切片元素被修改时更新其内部状态和数据结构,这不是预计。
数组在这方面很酷:数组意味着它的所有元素;复制数组复制所有元素,比较定义如下:
如果数组元素类型的值是可比较的,则数组值是可比较的。如果对应的元素相等,则两个数组值相等。
如果您修改之前用作键的数组元素:这不是问题,因为新数组(带有修改后的元素)不等于在地图中存储和使用的原始数组,因此查询与修改后的数组关联的值将有理由不产生任何结果,并且使用未修改的原始数组进行查询将正确地返回先前存储的值。
【讨论】: