错误示例
刚用go的时候,意图修改map中的某个结构体的某个成员的值,很多人都会下意识地这样写:
type Temp struct {
id int32
}
func main() {
// 声明一个结构体map
m := make(map[int32]Temp)
// 给map赋值
m[1] = Temp{1}
fmt.Println(m[1])
// 给map里的结构体的成员赋值
m[1].id= 2 // GoLand警告:Cannot take the address of 'm[1]'
fmt.Println(m[1].id)
}
此时编译会抛出错误:cannot assign to struct field m[1].id in map
错误分析
参考stack overflow:将值设置为结构作为映射中的值时,为什么会出现“无法分配”错误?
golang官方文档中提到:在进行Assignments(分配、赋值)时,左侧操作数必须是:可寻址的、映射索引表达式 或 空白标识符
映射索引表达式:即对数组或切片的索引进行赋值的形式:arr[0]=1
空白标识符:即下划线 _
可寻址的:文档中对可寻址的的链接如下。简单地说,能用地址运算符&
获得地址的,就是可寻址的
开头的错误示例,其实就是因为赋值符号(=
)左侧的m[1].id
是 不可寻址的
为什么?↓↓
m[1].id = 2
的意图是把map中key=1对应的value(结构体)的id值修改为2,而实际上这行go代码的正确拆解是:
t := m[1]
t.id = 2
经过拆解,我们首先应该知道:m[1].id = 2
的意图是无法实现的。这行代码并不能修改map中的数据,因为map的value是值类型的结构体。传递给t
的是拷贝自m[1]
的新结构体,和m[1]
指向的结构体数据一样,但是内存地址不一样。接下来的t.id = 2
,只会修改t
的值,和map中的数据没有关系
这就像是下面的例子。结构体是值类型的,如果变量的赋值指向一个值类型,那就是把这个值拷贝了一份给变量
func main() {
t1 := Temp{1}
t2 := t1
t2.id = 2
fmt.Printf("t1 地址 %p, 数据 %v \n", &t1, t1) // t1 地址 0xc00000a098, 数据 {1}
fmt.Printf("t2 地址 %p, 数据 %v \n", &t2, t2) // t2 地址 0xc00000a09c, 数据 {2}
}
然后回归原题,为什么m[1].id
是不可寻址的,从上面的拆解其实很容易解释:拆解中用t
来接收了m[1]
,然后把2
赋值给了t.id
。但m[1].id = 2
这行代码却没有变量接收m[1]
,这造成的问题是编译器不知道要把2
赋值到哪里去,无法寻址。也就抛出了了cannot assign to struct field m[1].id in map
正确的方式
有两种方式
一种是把map中的value(结构体)定义为指针类型。如下
type Temp struct {
id int32
}
func main() {
// 声明一个指针结构体map
m := make(map[int32]*Temp)
// 给map赋值
m[1] = &Temp{1}
fmt.Println(m[1]) // {1}
// 给map里的指针结构体的成员赋值
m[1].id= 2
fmt.Println(m[1].id) // 2
}
另一种是对map中的value进行替换
func main() {
// 声明一个结构体map
m := make(map[int32]Temp)
// 给map赋值
m[1] = Temp{1}
fmt.Println(m[1]) // {1}
// 替换map中的结构体
m[1] = Temp{2}
fmt.Println(m[1].id) // 2
}
end