Go语言方法和接收器类型详解

Go语言方法和接收器类型详解

1. 方法接收器类型

1.1 值接收器

值接收器方法不会改变接收器的状态,因为Go语言会在调用时复制接收器的值。因此,任何对接收器成员变量的修改都只会影响副本,而不会影响原始结构体实例。

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 指针接收器

指针接收器允许直接操作接收器本身,这意味着可以安全地修改接收器的字段,并且这些更改会持续存在。

go 复制代码
// 指针接收器方法
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 值类型调用指针接收器方法

go 复制代码
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 指针类型调用值接收器方法

go 复制代码
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值不可直接修改

go 复制代码
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 临时值不可寻址

go 复制代码
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 字面量不可寻址

go 复制代码
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值是指针类型的情况

go 复制代码
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编译器自动处理从指针到值的转换
  • 允许指针类型调用值接收器方法

注意事项

  1. 选择接收器类型时考虑:

    • 是否需要修改接收器状态
    • 性能考虑
    • 接口实现要求
  2. 处理不可寻址情况:

    • 使用中间变量
    • 正确处理map值的修改
    • 注意指针类型的使用
  3. 使用map存储指针时:

    • 可以直接修改指针指向的对象
    • 确保指针正确初始化
    • 考虑并发安全问题
相关推荐
yuanpan15 分钟前
23种设计模式之《组合模式(Composite)》在c#中的应用及理解
开发语言·设计模式·c#·组合模式
BanLul25 分钟前
进程与线程 (三)——线程间通信
c语言·开发语言·算法
十八朵郁金香30 分钟前
【JavaScript】深入理解模块化
开发语言·javascript·ecmascript
Hello.Reader38 分钟前
深入理解 Rust 的 `Rc<T>`:实现多所有权的智能指针
开发语言·后端·rust
程序员阿鹏41 分钟前
jdbc批量插入数据到MySQL
java·开发语言·数据库·mysql·intellij-idea
yoona102042 分钟前
Rust编程语言入门教程(八)所有权 Stack vs Heap
开发语言·后端·rust·区块链·学习方法
莲动渔舟43 分钟前
国产编辑器EverEdit - 在编辑器中对文本进行排序
java·开发语言·编辑器
滴_咕噜咕噜1 小时前
C#基础总结:常用的数据结构
开发语言·数据结构·c#
martian6651 小时前
【Java高级篇】——第16篇:高性能Java应用优化与调优
java·开发语言·jvm
考虑考虑2 小时前
MyCat2使用
java·后端·java ee