Go语言map类型注意事项

231 阅读2分钟

指针、slice、map、chan是go的引用类型,struct是值类型。函数中如果需要修改struct本身的值,需要传递struct指针作为参数。同时,如果声明值为struct的map,按key修改值时,也需要注意将map的value声明为struct指针类型,比如:

package main

import "fmt"

type student struct {
	Age int
	Name string
}

func main() {
	m := make(map[string]student)
	m["s1"] = student{30, "s1"}
	m["s2"] = student{31, "s2"}
	m["s3"] = student{32, "s3"}
	fmt.Println(m)

	m["s1"].Age = 33
	fmt.Println(m)
}

上面代码试图通过key修改对应结构体成员变量的值。结果编译时报错:

# command-line-arguments
./test.go:17:14: cannot assign to struct field m["s1"].Age in map

因为struct是值类型的,m["s1"]指向的不是实际的struct对象,go编译器这里直接给出了错误提示。将map值类型修改为student指针,可以解决此问题。

package main

import "fmt"

type student struct {
        Age int
        Name string
}

func main() {
        m := make(map[string]*student)
        m["s1"] = &student{30, "s1"}
        m["s2"] = &student{31, "s2"}
        m["s3"] = &student{32, "s3"}
        fmt.Println(m)

        m["s1"].Age = 33
        fmt.Println(m)
}

此时m["s1"]指向的是具体的指针对象,可以通过指针对象修改其成员变量。 再接着上面的例子,遍历访问该map,

package main

import "fmt"

type student struct {
	Age int
	Name string
}

func main() {
	m := make(map[string]*student)
	m["s1"] = &student{30, "s1"}
	m["s2"] = &student{31, "s2"}
	m["s3"] = &student{32, "s3"}
	fmt.Println(m)

	m["s1"].Age = 33

	for k, v := range m {
		fmt.Println(k, *v)
	}
}

运行该程序会发现每次的输出顺序都不同,第一次运行,

map[s1:0xc00000c060 s2:0xc00000c080 s3:0xc00000c0a0]
s1 {33 s1}
s2 {31 s2}
s3 {32 s3}

再运行一次,

map[s1:0xc000166000 s2:0xc000166020 s3:0xc000166040]
s2 {31 s2}
s3 {32 s3}
s1 {33 s1}

这是因为在底层实现上,range map调用了mapiterinit方法生成迭代器,在生成迭代器的过程中,首先产生一个随机数,并根据该随机数确定首先遍历的bucket的位置。所以每次产生的key顺序不同。