Go 中的地图初始化

IT小君   2022-11-10T08:32:19

据我了解,Go 中的类型slice和类型map在很多方面都相似。它们都是reference(或container)类型。在抽象数据类型方面,它们分别代表一个数组和一个关联数组。

但是,他们的行为完全不同。

var s []int
var m map[int]int

虽然我们可以立即使用声明的切片(追加新项目或重新切片),但我们无法对新声明的映射做任何事情。我们必须调用make函数并显式初始化地图。因此,如果某个结构包含一个映射,我们必须为该结构编写一个构造函数。

所以,问题是为什么不能在声明映射时添加一些语法糖并同时分配和初始化内存。

我确实谷歌了这个问题,学到了一个新词“avtovivification”,但仍然没有看到原因。

我不是在谈论结构文字。是的,您可以通过提供诸如m := map[int]int{1: 1}. 但是,如果您有一些struct

package main

import (
    "fmt"
)

type SomeStruct struct {
    someField map[int]int
    someField2 []int
}

func main() {
    s := SomeStruct{}
    s.someField2 = append(s.someField2, -1) // OK
    s.someField[0] = -1 // panic: assignment to entry in nil map
    fmt.Println(s)
}

不能立即使用结构(所有字段都使用默认值)。必须创建一个SomeStruct必须显式初始化地图的构造函数。

点击广告,支持我们为你提供更好的服务
评论(2)
IT小君

虽然我们可以立即使用声明的切片(追加新项目或重新切片),但我们无法对新声明的映射做任何事情。我们必须调用make函数并显式初始化地图。因此,如果某个结构包含一个映射,我们必须为该结构编写一个构造函数。

这不是真的。切片和映射的默认值(或更准确地说是零值nil)是. nil您可以对地图执行“相同”操作,就像对nil切片执行操作一样。您可以检查地图的长度nil,您可以索引nil地图(结果将是地图值类型的零值),例如以下所有工作:

var m map[int]int

fmt.Println(m == nil) // Prints true
fmt.Println(len(m))   // Prints 0
fmt.Println(m[2])     // Prints 0

在Go Playground上尝试一下

您对零值切片的“感觉”更多是您可以为其添加值。make()这是真的,但在后台,将使用您必须调用映射才能向其添加条目的确切内置函数分配一个新切片,并且您必须(重新)分配返回的切片。因此,零值切片比零值映射“不再可供使用”。append()只负责必要的(重新)分配和复制。我们可以有一个“等效”addEntry()函数,您可以向其传递一个映射值和键值对,如果传递的映射是nil,它可以分配一个新的映射值并返回它。如果您不调用append(),则无法向nil切片添加值,就像您无法向nil地图添加条目一样。

nil切片和映射的零值是(而不是初始化切片或映射)的主要原因是性能和效率。映射或切片值(变量或结构字段)通常不会被使用,或者不会立即使用,因此如果它们在声明时分配,那将浪费内存(和一些 CPU)资源,更不用说它为垃圾收集器提供了更多的工作。此外,如果零值是一个初始化值,它通常是不够的(例如,一个 0 大小的切片不能容纳任何元素),并且通常会在您向其添加新元素时被丢弃(因此初始分配将是完全浪费)。

是的,在某些情况下您确实想立即使用切片和映射,在这种情况下您可以调用make()自己,或者使用复合文字您还可以使用make()为地图提供(初始)容量的特殊形式,避免将来重新调整地图内部结构(这通常需要不可忽略的计算)。自动非nil默认值无法猜测您需要的容量。

2022-11-10T08:32:19   回复
IT小君

你可以!您正在寻找的是:

package main

import "fmt"

func main() {
    v := map[int]int{}

    v[1] = 1
    v[2] = 2

    fmt.Println(v)
}

:=是声明分配,而 asvar是简单地声明。

2022-11-10T08:32:19   回复