跳到主要内容

解决 Go 中结构体成员不能修改的问题的两种方法

· 阅读需 3 分钟
素明诚
Full stack development

map 引用类型的问题

在 Go 中,如果结构体的成员包含引用类型(如切片、map 或指针),当将该结构体作为 map 的值时,需要注意一些细节。具体来说,如果结构体的成员是引用类型,那么在获取结构体值并修改其引用类型成员时,会遇到不能修改的问题。

package main

import "fmt"

type Person struct {
name string
age int
}

func main() {
m := make(map[string]Person)

// 添加键值对
m["john"] = Person{"John", 30}

// 获取 john 对应的 Person 结构体值
john := m["john"]

// 修改 john 的 age 字段
john.age = 40

// 此时输出 map 中 "john" 对应的 age 字段值
fmt.Println(m["john"].age) // 输出 30,而不是 40
}


我们创建了一个 Person 结构体类型,并将其作为值存储在了一个 map 中。然后,我们从 map 中取出了键为 "john" 的 Person 结构体值,并尝试修改其 age 字段的值。但是,即使我们修改了 johnage 字段,最终打印出来的结果仍然是原来 map 中的值,即 30,而不是修改后的 40。这是因为在 Go 中,m["john"] 返回的是 Person 结构体的值的副本,而不是原始值的引用。因此,修改 john 并不会影响原始值在 map 中的值

如何修改呢?

第一种方式

可以通过使用指针类型来存储结构体值,或者在获取结构体值时使用指针,并且修改成员时也要通过指针来操作。

package main

import "fmt"

type Person struct {
name string
age int
}

func main() {
m := make(map[string]*Person)

// 添加键值对,值为 Person 结构体的指针
m["john"] = &Person{"John", 30}

// 获取 john 对应的 Person 结构体指针
john := m["john"]

// 修改 john 指向的结构体的 age 字段
john.age = 40

// 此时输出 map 中 "john" 对应的 age 字段值
fmt.Println(m["john"].age) // 输出 40
}


我们将 map 中的值类型指定为 *Person,即 Person 结构体的指针。当我们获取键为 "john" 的值时,会得到 Person 结构体的指针,然后可以通过指针来修改结构体的字段。

第二种

package main

import "fmt"

type Person struct {
name string
age int
}

func main() {
m := make(map[string]Person)

// 添加键值对
m["john"] = Person{"John", 30}

// 获取 john 对应的 Person 结构体值的指针
john := &m["john"]

// 修改 john 指向的结构体的 age 字段
john.age = 40

// 此时输出 map 中 "john" 对应的 age 字段值
fmt.Println(m["john"].age) // 输出 40
}


先获取了键为 "john" 的 Person 结构体值的指针,并将其赋给 john 变量。然后,通过指针来修改 john 指向的结构体的字段。

使用建议

建议使用第一种方式,使用指针可以避免复制结构体值,提高效率,并且可以确保所有修改都是针对同一个结构体值的引用,避免了值的复制和不一致的状态。另外,使用指针还可以避免因为值复制而导致的内存占用过多的问题,尤其是在处理大型结构体时更为明显。