切片和映射之间的行为差​​异

Yol*_*Voe 3 go

相关问题在这里/sf/answers/907611071/

在go中,你可以这样做:

func numsInFactorial(n int) (nums []int) {
    // `nums := make([]int)` is not needed
    for i := 1; i <= n; i++ {
        nums = append(nums, i)
    }
    return
}
Run Code Online (Sandbox Code Playgroud)

但是,以下方法不起作用:

func mapWithOneKeyAndValue(k int, v int) (m map[int]int) {
    m[k] = v
    return
}
Run Code Online (Sandbox Code Playgroud)

抛出错误:

panic: assignment to entry in nil map

相反,您必须:

func mapWithOneKeyAndValue(k int, v int) map[int]int {
    m := make(map[int]int)
    m[k] = v
    return
}
Run Code Online (Sandbox Code Playgroud)

我找不到此行为的文档。我已经通读了所有 effective go 的内容,那里也没有提到它。
我知道命名返回值已定义(即内存已分配;接近于分配的new内存)但未初始化(因此make行为未复制)。
经过一些实验,我相信这种行为可以简化为理解以下代码的行为:

func main() {
    var s []int // len and cap are both 0
    var m map[int]int

    fmt.Println(s) // works... prints an empty slice
    fmt.Println(m) // works... prints an empty map

    s = append(s, 10) // returns a new slice, so underlying array gets allocated
    fmt.Println(s) // works... prints [10]

    m[10] = 10 // program crashes, with "assignment to entry in nil map"
    fmt.Println(m)
}
Run Code Online (Sandbox Code Playgroud)

该问题似乎append可能会调用make并分配一个新切片,检测到 的容量s0。但是,map永远不会得到显式初始化。
提出这个问题的原因有两个。首先,我想记录一下 SO 上的行为。slice其次,为什么该语言允许和的非初始化定义map?根据我到目前为止使用 go 的经验,它似乎是一种实用语言(即未使用的变量会导致编译失败,gofmt 会强制进行正确的格式化),因此它阻止代码编译是有意义的。

Mik*_*ail 5

尝试按索引分配 nil 切片 - 您将得到“恐慌:运行时错误:索引超出范围”(示例: https: //play.golang.org/p/-XHh1jNyn5g

追加函数使用 nil 的唯一原因是追加函数可以对给定的切片进行重新分配。例如,如果您尝试将第 6 个元素追加到当前容量为 5 的 5 个元素的切片中,它将创建具有新容量的新数组,复制旧数组中的所有信息,并交换给定切片中的数据数组指针。在我的理解中,这只是动态数组的golang实现。

因此,nil 切片只是容量不足的切片的一种特殊情况,因此它会在任何追加操作中重新分配。

更多详细信息请访问https://blog.golang.org/go-slices-usage-and-internals

  • @YoloVoe,请注意,在未初始化的地图中_search_也完全可以:它会按预期工作。因此,这个答案正确地抓住了差异的要点:我想,您可以“附加”到未初始化的切片的事实是一种特殊情况,太方便了,不能忽略。另请考虑 Go 有第三种需要初始化的类型:通道。就像地图一样,“nil”通道在某些情况下是有效的,但您无法发送给它们。 (2认同)