为什么不允许获取地图值的地址?

时间:2018-03-09 21:01:17

标签: pointers dictionary go

这与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])

}

2 个答案:

答案 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也可以。