「这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战」
首先说明 Go 参数传递都是值传递。
看个例子:
func alter_map(a map[int]int) {
a[1] = 2
fmt.Printf("%p\n", a) //查看修改【后】的map的内存地址
}
func TestChangeMap2(t *testing.T) {
a := make(map[int]int, 2)
a[2] = 1
fmt.Printf("%p\n", &a) //查看修改【前】的map的内存地址
alter_map(a) //修改map
fmt.Println(a)
}
运行结果:
=== RUN TestChangeMap2
0xc00000e038
0xc000074360
map[1:2 2:1]
--- PASS: TestChangeMap2 (0.00s)
PASS
说明内存地址改变了,发现地址传递前后是完全不一样的,发生了数据拷贝。,但是为啥函数内部又可以修改 外部调用者的值呢? 虽然函数内部,函数调用者 是两个 map ,但是函数调用者调用函数时,会将函数外部的 map 存储结构的指针 拷贝到函数内部 map 的对应存储结构中,函数内部修改存储结构上的书,对外部也是可见的,因为执行的同一存储结构上的地址。
在看个例子:
func TestChangeMap3(t *testing.T) {
a := make(map[int]int, 2)
a[2] = 1
fmt.Printf("%p\n", &a) //查看修改【前】的map的内存地址
alterMap(&a) //修改map
fmt.Println(a)
}
func alterMap(a *map[int]int) {
b := make(map[int]int)
b[1] = 2
*a = b
fmt.Printf("%p\n", a) //查看修改【后】的map的内存地址
}
运行结果:
=== RUN TestChangeMap3
0xc0000aa028
0xc0000aa028
map[1:2]
--- PASS: TestChangeMap3 (0.00s)
PASS
内存地址没有变,但是内容变了。
map 作为入参进行传递
当用make函数定义一个map时,make返回了一个Type类型,看似好像返回了一个值,而非指针,其实不然,其实还是通过指针进行传递都。
m := make(map[int]int)
func make(t Type, size ...IntegerType) Type
map 传递入参
func TestMapChuangti(t *testing.T) {
m := make(map[int]int)
m[1] = 1
ChangeMap(m)
fmt.Println(m)
}
//修改map
func ChangeMap(m map[int]int) {
m[1] = 0
}
运行结果:
修改前: map[1:1]
修改后: map[1:0]
有个坑,如果把map修改为nil,则达不到修改map的目的。比如把ChangeMap函数改为:
func ChangeMap1(m map[int]int){
m = nil
}
运行结果:
修改前: map[1:1]
修改后: map[1:1]
可以发现,map值并没有修改成nil。 结果:
- `map·作为函数入参是作为指针进行传递的
- 函数里面对map进行修改时,会同时修改源map的值,但是将map修改为nil时,则达不到预期效果。
json umarshal 问题
json: Unmarshal(non-pointer map[string]string)
看个例子:
func TestJsonUnmarshal_err(t *testing.T) {
m := make(map[string]string)
data := `{"foo": "bar"}`
err := json.Unmarshal([]byte(data), m)
if err != nil {
fmt.Println(err)
}
fmt.Println(m)
}
上述代码执行结果:
=== RUN TestJsonUnmarshal_err
json: Unmarshal(non-pointer map[string]string)
map[]
--- PASS: TestJsonUnmarshal_err (0.00s)
PASS
执行结果中国,报错了一个 json: Unmarshal(non-pointer map[string]string)
func TestJsonUnmarshal_pass(t *testing.T) {
m := make(map[string]string)
data := `{"foo": "bar"}`
err := json.Unmarshal([]byte(data), &m)
if err != nil {
fmt.Println(err)
}
fmt.Println(m)
}
执行结果:
=== RUN TestJsonUnmarshal_pass
map[foo:bar]
--- PASS: TestJsonUnmarshal_pass (0.00s)
PASS
出现上述结果,为啥呢?
原因说明, Unmarshal 可以映射到变量上 map, slice, 如果我们传递一个变量 map, 2不是 指向 map , 然后新的映射的变量对调用者来说是不可以见的。
看个例子:
func mapFunc(m map[string]interface{}) {
m = make(map[string]interface{}) // 如果注释掉会包 panic assignment to entry in nil map
m["abc"] = "123"
}
func mapPtrFunc(mp *map[string]interface{}) {
m := make(map[string]interface{})
m["abc"] = "123"
*mp = m
}
func TestJsonUnmarshal_example(t *testing.T) {
var m1, m2 map[string]interface{}
mapFunc(m1)
mapPtrFunc(&m2)
fmt.Printf("%+v, %+v\n", m1, m2)
}
运行结果:
=== RUN TestJsonUnmarshal_example
map[], map[abc:123]
--- PASS: TestJsonUnmarshal_example (0.00s)
PASS
如果要一个局部变量对调用者可见, 一般需要满足下面两种情况:
- a. 局部变量是函数的返回值
- b. 局部变量赋值到函数/方法的入参中
go 中的一切都是值传递, 对于 b 这个情况,入参必须是一个指针。
上面的例子说明:
m1和m2指向一个nil- 调用
mapFunc会拷贝值指针m1到m, 因此m也是个nilmap - 如果在步骤1 中
m1已经初始化了,再步骤2 参数传递过程中存储m1map 内容结构的地址(不是m1的地址)会拷贝到m中 这样的话,m1和m都指向相同的 map 数据结构,修改m1的值,会对m可见。 - 但是 在
mapFunc中是是新建了一个map. 这个不能对m1可见
对于 mapPtrFunc 函数:
- 当调用
mapPtrFunc时,是将m2的指针地址(不是存储数据结构的地址) 拷贝给mp - 在
mapPtrFunc中, 新建的 mapm赋值给*mp(不是mp), 也就是说指针mp指向的是m2, 现在指针mp指向了新了的m也就指向了新的数据结构。
func TestChangeMap3(t *testing.T) {
a := make(map[int]int, 2)
a[2] = 1
fmt.Printf("a %p\n", a) //查看修改【前】的map a 的内存地址
fmt.Printf("&a %p\n", &a) //查看修改【前】的map a 的内存地址
alterMap(&a) //修改map
fmt.Printf("a %p\n", a) //查看修改【后】的map a的内存地址
fmt.Println(a)
}
func alterMap(a *map[int]int) {
b := make(map[int]int)
b[1] = 2
fmt.Printf("b %p\n", b) //map b 的内存地址
*a = b
fmt.Printf("&a %p\n", a) //查看修改【后】的map a 指针的内存地址
}
执行结果:
=== RUN TestChangeMap3
a 0xc000100300
&a 0xc00010e028
b 0xc000100330
&a 0xc00010e028
a 0xc000100330
map[1:2] // 原来的值 a[2] = 1 没了
--- PASS: TestChangeMap3 (0.00s)
PASS
关于指针 a, &a 的问题:
func main(){
a := 10
b := &a
fmt.Printf("a:%d ptr:%p\n", a, &a) //a:10 ptr:0xc00001a078
fmt.Printf("b:%p type:%T\n", b, b) //b:0xc00001a078 type:*int
fmt.Println(&b) // 0xc00000e018
}