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存储指针时:

    • 可以直接修改指针指向的对象
    • 确保指针正确初始化
    • 考虑并发安全问题
相关推荐
iOS开发上架哦几秒前
Swift中对象实例方法名混淆问题详细解决方法
后端
零日失眠者几秒前
【文件管理系列】003:重复文件查找工具
后端·python
哈哈哈笑什么3 分钟前
多级缓存框架(Redis + Caffeine)完整指南
redis·后端
哈哈哈笑什么3 分钟前
分布式事务实战:订单服务 + 库存服务(基于本地消息表组件)
分布式·后端·rabbitmq
溪饱鱼11 分钟前
NextJs + Cloudflare Worker 是出海最佳实践
前端·后端
哈哈哈笑什么11 分钟前
完整分布式事务解决方案(本地消息表 + RabbitMQ)
分布式·后端·rabbitmq
小周在成长25 分钟前
Java 抽象类 vs 接口:相同点与不同点
后端
expect7g26 分钟前
Paimon Branch --- 流批一体化之二
大数据·后端·flink
幌才_loong27 分钟前
.NET 8 实时推送魔法:SSE 让数据 “主动跑” 到客户端
后端
未来之窗软件服务29 分钟前
操作系统应用(三十三)php版本选择系统—东方仙盟筑基期
开发语言·php·仙盟创梦ide·东方仙盟·服务器推荐