切片和地图之间的行为差​​异

时间:2018-10-06 17:50:48

标签: go

一个相关的问题在这里https://stackoverflow.com/a/12965872/6421681

在旅途中,您可以执行以下操作:

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

但是,以下操作无效:

func mapWithOneKeyAndValue(k int, v int) (m map[int]int) {
    m[k] = v
    return
}

引发错误:

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
}

我找不到有关此行为的文档。 我已经阅读了所有有效的建议,也没有提及。
我知道已定义了命名的返回值(即,已分配内存;接近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)
}

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

2 个答案:

答案 0 :(得分:4)

尝试按索引在nil slice中分配-您将收到“紧急情况:运行时错误:索引超出范围”(例如:https://play.golang.org/p/-XHh1jNyn5g

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

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

有关https://blog.golang.org/go-slices-usage-and-internals的更多详细信息

答案 1 :(得分:3)

来自https://blog.golang.org/go-maps-in-action

  

读取时,nil映射的行为类似于空映射,但是尝试写入nil映射会导致运行时出现紧急情况;不要那样做要初始化地图,请使用内置的make函数

似乎nil映射被认为是有效的空映射,这就是他们不自动为其分配内存的原因。