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

    • 可以直接修改指针指向的对象
    • 确保指针正确初始化
    • 考虑并发安全问题
相关推荐
子兮曰15 小时前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰15 小时前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
小羊没烦恼!16 小时前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
爱勇宝16 小时前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
胡写代码16 小时前
别再前后端各写一套表单校验了
java·后端
伞伞悦读17 小时前
【第38期】Python 模块与包详解:import、from、模块搜索路径、包结构和 __init__
开发语言·python
ttwuai17 小时前
Go开源后台管理系统推荐:怎么按技术栈和边界比较4个官方仓库?
golang·gin
大勇前进17 小时前
原生 PHP 还是 Laravel?小项目到底要不要上框架
后端
yuzhi_liu17 小时前
我用 LangGraph4j 实现 Multi-Agent Supervisor
后端
alsmile17 小时前
Node-RED 之外,国产规则引擎的新方案:基于标准语法,Go 先行实现
后端·开源·go