Go中结构体映射和结构体数组的对比

10

假设我有一个简单的结构体a,其中包含一个字符串属性b:

type A struct {
    B string
}
以下代码使用一个 A 类型的数组:
testArray := []A{A{}}
testArray[0].B = "test1"
fmt.Println(testArray[0].B)

将如预期地打印出"test1"。

但是这段代码看起来同样简单:

testMap := make(map[string]A)
testMap["key"] = A{}
testMap["key"].B = "test2"
fmt.Println(testMap["key"].B)

将子属性分配给Map会导致错误:无法分配给testMap ["key"] .B,而不是输出“test2”。那么,为什么将子属性分配给数组时可以正常工作,而将子属性分配给Map会导致错误呢?我想知道为什么对于Map不起作用,而对于数组却起作用。我也很想猜测一下他们为什么在这两个数据结构之间设计了这种差异。


testArray不是一个“数组”。它是一个“切片”。 “数组”是另一回事。 - newacct
4个回答

14

我在邮件列表上做了详细回答,但简短的解释是这不起作用,因为映射条目是不可寻址的。这意味着您无法获取映射中条目的地址。这是因为向映射中添加新值可能会导致映射条目移动,因此地址会发生变化。由于无法获取映射中条目的地址,所有映射操作都使用整个值:从映射中复制整个值,将整个值添加到映射中。在映射中赋值结构体的一个字段需要进行读取-修改-写入操作,而映射不支持该操作(它们可以支持,但是不支持,并且支持它们需要付出成本)。

数组和切片中的元素是可寻址的,因为它们创建后不会移动。


1
这回答了我的问题。它不是“只是因为它是这样设计的”或“魔法”之类的东西,数组允许这样做而映射不允许的原因是有道理的。谢谢! - GreenMachine
1
此外,你深入的解答更加有助益。或许将全部内容发布在这里也是值得的,但暂时先提供以下链接:https://groups.google.com/d/msg/golang-nuts/FCcLsuWsF_U/qk6SLNcHvJIJ - GreenMachine

5
问题在于,在地图示例中,testMap["key"]返回的是一个字面量,而不是一个指针。这意味着修改它是没有意义的,因此编译器会禁止这样做。这基本上相当于:
v := testMap["key"]
v.B = "test2"

......然后再也不使用v。它没有任何效果,相当于根本没有执行那两行代码。这就是编译器不允许你这么做的原因。另一方面,如果你把它作为指向A的指针的映射,那么你可以做到。以下代码可以编译通过:

testMap := make(map[string]*A)
testMap["key"] = &A{}
testMap["key"].B = "test2"

这个方法有效的原因在于对指针值进行解引用并赋值产生影响。

我喜欢那个关于映射的解释,但数组有什么不同呢?数组也不返回指针,所以为什么在数组示例中这样做可以工作呢? - GreenMachine
我认为这只是语言设计的方式。数组是神奇的。理论上,他们也可以用映射实现,但出于某种原因,他们没有这样做,我想。 - joshlf
这是一个“临时”的而不是“字面上的”。 - Matt K
数组并不神奇。对于数组或切片的索引,它是相对于其起始位置的偏移量。对于映射的索引,它会返回存储桶中某个项的地址,但随着添加更多项,该地址可能会变得无效。 - Matt K
另外,@mkb,你能解释一下什么是临时变量吗?或者提供一个解释的链接吗? - joshlf
显示剩余3条评论

2
数组元素是一个lvalue。在使用映射时,情况会更加复杂。例如:
m := map[T]U{}
m[T(expr)] = U(expr)

左边的表达式 m[T(expr)] 是一个 lvalue。然而,在以下情况下:
type U struct{
        F V
}

m := map[T]U{}
m[T(expr)].F = 34

LHS的[T(expr)].F不再是lvalue。第一部分,m[T(expr)]计算为类型U的实例。该实例是“浮动的”,它不再有归属。给其字段赋值肯定是错误的,因此编译器会报错。
这与以下两者之间的区别基本相同:
var v U
v.F = 42 // ok
U{}.F = 42 // not ok

为解决这个问题,您可以使用指向结构体的指针:
m := map[T]*U{}
m[T(expr)].F = 42

这句话的意思是:“地图首先提供了指向U的指针,然后用它来设置该字段。”

1

从语言参考的技术角度来看,表达式testmap["key"].B不是可寻址的,因此不能用作赋值的左操作数。

因此问题可能需要转换为:为什么该表达式不可寻址?我还不太确定...

啊...这是因为testmap["key"]返回给我们一个结构的副本。改变该副本可能不是我们想要做的事情,因为它不会影响映射中的原始结构。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接