[英]Map initialization in Go
据我了解,类型slice
和map
在 Go 中在很多方面都相似。 它们都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
创建一个构造函数,该函数必须显式初始化映射。
虽然我们可以立即使用声明的切片(附加新项目或重新切片),但我们不能对新声明的映射做任何事情。 我们必须调用
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()
函数,您可以addEntry()
函数传递一个映射值和键值对,如果传递的映射是nil
,它可以分配一个新的映射值并返回它。 如果不调用append()
,则无法向nil
切片添加值,就像无法向nil
映射添加条目一样。
切片和映射的零值nil
(而不是初始化的切片或映射)的主要原因是性能和效率。 通常情况下,映射或切片值(变量或结构字段)永远不会被使用,或者不会立即被使用,因此如果它们在声明时分配,那将浪费内存(和一些 CPU)资源,更不用说它为垃圾收集器提供了更多工作。 此外,如果零值是一个初始化值,它通常是不够的(例如,0 大小的切片不能容纳任何元素),并且通常会在您向其中添加新元素时被丢弃(因此初始分配将是完全浪费)。
是的,在某些情况下,您确实想立即使用切片和映射,在这种情况下,您可以自己调用make()
或使用复合文字。 您还可以使用特殊形式的make()
为地图提供(初始)容量,避免未来对地图内部结构的重构(这通常需要不可忽略的计算)。 自动非nil
默认值猜不透你需要什么样的能力。
你可以! 你要找的是:
package main
import "fmt"
func main() {
v := map[int]int{}
v[1] = 1
v[2] = 2
fmt.Println(v)
}
:=
是声明和分配,而var
只是声明。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.