Go语言方法和接收器类型详解
1. 方法接收器类型
1.1 值接收器
值接收器方法不会改变接收器的状态,因为Go语言会在调用时复制接收器的值。因此,任何对接收器成员变量的修改都只会影响副本,而不会影响原始结构体实例。
type Person struct {
Name string
Age int
}
// 值接收器方法
func (p Person) GetAge() int {
return p.Age
}
func TestValueReceiver(t *testing.T) {
p := Person{Name: "Tom", Age: 20}
if age := p.GetAge(); age != 20 {
t.Errorf("Expected age 20, got %d", age)
}
// p的值在方法调用后不会改变
}
1.2 指针接收器
指针接收器允许直接操作接收器本身,这意味着可以安全地修改接收器的字段,并且这些更改会持续存在。
// 指针接收器方法
func (p *Person) SetAge(age int) {
p.Age = age
}
func TestPointerReceiver(t *testing.T) {
p := &Person{Name: "Tom", Age: 20}
p.SetAge(21)
if p.Age != 21 {
t.Errorf("Expected age 21, got %d", p.Age)
}
// p的值会被修改
}
2. 值类型和指针类型的方法调用
2.1 值类型调用指针接收器方法
func TestValueTypeCallPointerMethod(t *testing.T) {
p := Person{Name: "Tom", Age: 20}
p.SetAge(21) // Go 自动转换为 (&p).SetAge(21)
if p.Age != 21 {
t.Errorf("Expected age 21, got %d", p.Age)
}
}
2.2 指针类型调用值接收器方法
func TestPointerTypeCallValueMethod(t *testing.T) {
p := &Person{Name: "Tom", Age: 20}
age := p.GetAge() // Go 自动转换为 (*p).GetAge()
if age != 20 {
t.Errorf("Expected age 20, got %d", age)
}
}
3. 不可寻址的情况
3.1 Map值不可直接修改
func TestMapValueNotAddressable(t *testing.T) {
people := make(map[string]Person)
people["tom"] = Person{Name: "Tom", Age: 20}
// 错误做法
// people["tom"].Age = 21
// 正确的做法
person := people["tom"]
person.Age = 21
people["tom"] = person
if people["tom"].Age != 21 {
t.Errorf("Expected age 21, got %d", people["tom"].Age)
}
}
3.2 临时值不可寻址
func TestTemporaryValueNotAddressable(t *testing.T) {
// 以下代码会编译错误
// Person{Name: "Tom", Age: 20}.SetAge(21)
// 可以调用值接收器方法
age := Person{Name: "Tom", Age: 20}.GetAge()
if age != 20 {
t.Errorf("Expected age 20, got %d", age)
}
}
3.3 字面量不可寻址
func TestLiteralNotAddressable(t *testing.T) {
// 以下代码会编译错误
// (&struct{ name string }{"tom"}).name = "jerry"
// 正确的做法是先赋值给变量
p := Person{Name: "Tom", Age: 20}
p.SetAge(21)
if p.Age != 21 {
t.Errorf("Expected age 21, got %d", p.Age)
}
}
3.4 Map值是指针类型的情况
func TestMapWithPointerValues(t *testing.T) {
// 创建一个字符串到 *Person 的映射
people := make(map[string]*Person)
// 添加一个新的 Person 到映射中
people["tom"] = &Person{Name: "Tom", Age: 20}
// 直接修改 tom 的年龄
people["tom"].Age = 21
if people["tom"].Age != 21 {
t.Errorf("Expected age 21, got %d", people["tom"].Age)
}
}
4. Map值的正确修改方式
- 获取值的副本
- 修改副本
- 将修改后的副本存回map
5. 方法调用的自动转换
5.1 值到指针的自动转换
- Go编译器自动处理从值到指针的转换
- 允许值类型调用指针接收器方法
5.2 指针到值的自动转换
- Go编译器自动处理从指针到值的转换
- 允许指针类型调用值接收器方法
注意事项
-
选择接收器类型时考虑:
- 是否需要修改接收器状态
- 性能考虑
- 接口实现要求
-
处理不可寻址情况:
- 使用中间变量
- 正确处理map值的修改
- 注意指针类型的使用
-
使用map存储指针时:
- 可以直接修改指针指向的对象
- 确保指针正确初始化
- 考虑并发安全问题