这与Why does go forbid taking the address of (&) map member, yet allows (&) slice element?相同,但我对接受的答案不满意:"切片由支持数组支持,地图不支持。"
注意:我现在已经为引用的问题添加了自己的答案 上方。
问题Access Struct in Map (without copying)甚至更好,但其接受的答案是说您无法修改地图中结构值的字段,因为您无法获取其地址(这是我的问题)。
地图由内存结构(可能包括数组)支持,就像切片一样。
那么我为什么不能获取地图值的地址的真正原因是什么呢?
我想修改一个map struct值。可以使用++或+ =
等运算符在适当的位置修改地图中的数字值 func icandothis() {
cmap := make(map[int]complex64)
cmap[1] += complex(1, 0)
fmt.Println(cmap[1])
}
但是结构值不能修改:
type Complex struct {
R float32
I float32
}
func (x *Complex) Add(c Complex) {
x.R += c.R
x.I += c.I
}
func but_i_cannot_do_this() {
cmap := make(map[int]Complex)
//cmap[1].Add(Complex{1, 0})
fmt.Println(cmap[1])
}
func so_i_have_to_do_this() {
cmap := make(map[int]Complex)
c := cmap[1]
c.Add(Complex{1, 0})
cmap[1] = c
fmt.Println(cmap[1])
}
答案 0 :(得分:13)
让我们从这个错误的主张开始:
我想修改一个map struct值。地图中的数字值 可以使用++或+ =
等运算符进行适当的修改func icandothis() { cmap := make(map[int]complex64) cmap[1] += complex(1, 0) fmt.Println(cmap[1]) }
让我们扩展速记形式:
package main
import (
"fmt"
)
func icandothisShort() {
cmap := make(map[int]complex64)
cmap[1] += complex(1, 0)
fmt.Println(cmap[1])
}
func icandothisLong() {
cmap := make(map[int]complex64)
// An assignment operation x op= y where op is a binary arithmetic operator
// is equivalent to x = x op (y) but evaluates x only once.
// cmap[1] += complex(1, 0)
v := cmap[1] // v = zero value = complex(0, 0)
v = v + complex(1, 0) // v = complex(0, 0) + complex(1, 0) = complex(1, 0)
cmap[1] = v // cmap[1] = v = complex(1, 0)
a := cmap[1] // a = complex(1, 0)
fmt.Println(a) // complex(1, 0)
}
func main() {
icandothisShort()
icandothisLong()
}
游乐场:https://play.golang.org/p/1OgmI_AD9uN
输出:
(1+0i)
(1+0i)
正如您在icandothisLong()
中看到的icandothisShort()
的扩展形式,没有就地更新。
下一个虚假声明,
地图由内存结构(可能包括数组)支持 像切片一样。
那么我无法获取地图地址的真正原因是什么呢? 值?
真正的原因是你不了解地图数据结构。
地图由存储桶内存结构支持。映射密钥通过不完美的动态散列标识当前的主存储桶。映射键和值存储在主存储桶或溢出存储桶中。随着地图条目的创建,更新和删除,地图存储桶会不断重组。 映射条目在内存中没有固定的位置。
做一些基础研究。例如,
GopherCon 2016: Keith Randall - Inside the Map Implementation
答案 1 :(得分:1)
因为替代方案是没有delete
功能。请考虑以下示例。
m := map[int]int{1: 2}
v := &m[1]
delete(m, 1)
v
指向什么?
有四种可能的答案(嗯,更多,但它们同样糟糕),但没有一种是令人满意的。
标记缺失条目的墓碑。这将不允许在从哈希表中删除条目后重用该条目,这将需要更多常见的大小调整,并且会在通常删除元素的地图中浪费内存。
悬空指针,与Go内存安全要求不兼容。
这是一个无效的代码。需要运行时检查所有指针访问或实现Rust样式借用检查器。
要求值位于添加间接的指针后面。您可以使用map[int]*int
等类型自行完成。
您可能会说程序在删除后不应访问指向map元素的指针。这在内存不安全的编程语言中会很好,Go不是。
顺便说一句,出于参考目的,其他地图操作肯定可以实现,同时允许引用地图元素。遵循C ++迭代器失效要求的地图实现将很容易得到 - 尽管由于C ++映射相对较慢而存在成本。但是,如果大多数C ++程序可以管理这个成本,Go也可以。