10

假设我有一个带有字符串属性 b 的简单结构 a:

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)

不会打印出“test2”,而是会导致以下错误:

无法分配给 testMap["key"].B

那么,为什么分配给映射中的子属性会导致错误,而分配给数组中的子属性会按预期工作呢?我想知道为什么这对地图不起作用以及为什么它对数组起作用。我也很想猜测他们为什么设计了两种数据结构之间存在这种差异的语言。

4

4 回答 4

13

我在邮件列表上回答了一些长度,但简短的解释是这不起作用,因为地图条目不可寻址。这意味着您不能获取地图中条目的地址。那是因为向映射添加新值可能会导致映射条目移动,从而导致地址发生变化。因为您不能获取映射中条目的地址,所以所有映射操作都使用完整值:从映射中复制整个值,将整个值添加到映射中。分配给映射中结构的一个字段将需要读取-修改-写入操作,映射不支持(它们可以,但它们不支持,并且支持它们是有成本的)。

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

于 2013-08-06T18:58:54.930 回答
5

问题是在地图示例中,testMap["key"]返回的是文字,而不是指针。这意味着修改它是没有意义的,所以编译器不允许它。它基本上相当于:

v := testMap["key"]
v.B = "test2"

...然后再也不使用v了。它没有效果。这相当于一开始就从不执行这两行。这就是编译器不允许你这样做的原因。另一方面,如果你把它做成一个指向A 的地图,你就会做生意。这编译:

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

这样做的原因是取消引用和分配给指针值确实有效果。

于 2013-08-06T14:50:17.133 回答
2

数组元素是一个左值。使用地图,它有点复杂。在:

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

LHSm[T(expr)] 一个左值。但是,在:

type U struct{
        F V
}

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

LHSm[T(expr)].F不再是左值。第一部分,m[T(expr)]评估为 type 的实例U。那个实例是“浮动的”,它已经没有家了。给它的字段赋值肯定是一个错误,所以编译器大喊大叫。

它或多或少与以下之间的区别相同:

var v U
v.F = 42 // ok
U{}.F = 42 // not ok

顶部解决问题,可以使用指向结构的指针:

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

映射首先产生一个指针U,然后用于设置字段。

于 2013-08-06T14:40:44.343 回答
1

从技术上讲,根据语言参考,表达式testmap["key"].B是不可寻址的,因此不能用作赋值的左侧。

所以问题可能需要转移到:为什么该表达式不可寻址?我还不太确定...

……啊。这是因为testmap["key"]它给了我们一个结构的副本。改变那个副本可能不是我们想要做的,因为它不会影响地图中的原始结构。

于 2013-08-06T14:43:50.993 回答