golang方法指针接收者和值接收者

在 Go(Golang)中,*方法的接收者(receiver)可以是值类型(T)也可以是指针类型(T) 。它们在行为、调用方式、性能等方面存在重要区别。下面将以最详细的方式,从多个维度进行讲解,包括内存、行为差异、调用规则、场景选择、示例代码和易错点。


🧠 一、基本概念

  • 值接收者(T) :方法的接收者是对象的一个副本。
  • *指针接收者(T) :方法的接收者是对象的地址,可以修改对象本身的值。
go 复制代码
type Person struct {
    Name string
}
​
// 值接收者
func (p Person) SayHello() {
    p.Name = "张三"
    fmt.Println("Hello", p.Name)
}
​
// 指针接收者
func (p *Person) Rename(newName string) {
    p.Name = newName
}

📌 二、值接收者 VS 指针接收者 对比表

对比维度 值接收者(T) 指针接收者(*T)
是否能修改接收对象? ❌ 不能修改原值(操作的是副本) ✅ 可以修改原值(操作的是指针)
内存开销 ✅ 内存较小(小对象更适合) 📉 略大,特别是对象较大时指针更高效
接收者是否复制? ✅ 是(拷贝结构体) ❌ 否(直接引用)
是否自动转换? ✅ 方法可以被指针和值都调用 ✅ 方法可以被指针和值都调用(语法糖)
适用于哪些情况? 不修改对象,适合小对象 需要修改对象状态,或者对象比较大

🧪 三、示例代码详解

go 复制代码
package main
​
import "fmt"
​
type Person struct {
    Name string
}
​
// 值接收者
func (p Person) Say() {
    fmt.Println("值接收者:", p.Name)
    p.Name = "值改名"
}
​
// 指针接收者
func (p *Person) Speak() {
    fmt.Println("指针接收者:", p.Name)
    p.Name = "指针改名"
}
​
func main() {
    p := Person{Name: "原名"}
​
    p.Say()     // 值接收者:打印"原名",但不会改变 p.Name
    fmt.Println("调用Say后:", p.Name) // 仍是"原名"
​
    p.Speak()   // 指针接收者:打印"原名",并修改为"指针改名"
    fmt.Println("调用Speak后:", p.Name) // 是"指针改名"
}

🔄 四、自动调用规则(方法集)

方法集定义:

  • 对于 T 类型:可以调用接收者为 T 的方法。
  • 对于 *T 类型:可以调用接收者为 T 和 *T 的方法。

自动转换示例:

go 复制代码
type Dog struct {
    Name string
}
​
func (d Dog) Bark() {
    fmt.Println("Bark", d.Name)
}
​
func (d *Dog) SetName(name string) {
    d.Name = name
}
​
func main() {
    var d1 Dog = Dog{Name: "旺财"}
    var d2 *Dog = &Dog{Name: "小白"}
​
    d1.Bark()        // OK
    d1.SetName("狗蛋") // OK 自动转为指针类型调用
    fmt.Println(d1.Name) // Name 没变(因为是值接收者)
​
    d2.Bark()        // OK 自动解引用调用
    d2.SetName("狗剩") // OK
    fmt.Println(d2.Name) // Name 改变了
}

📦 五、实际应用场景建议

使用场景 推荐接收者类型
结构体很小(比如 1-2 字段),无需修改对象 值接收者(T)
结构体较大或需要修改字段(比如 list、map、字段多) 指针接收者(*T)
实现接口(如果接口定义用的是指针接收者) 必须用指针接收者
并发安全、共享状态等涉及引用传递 指针接收者

⚠️ 六、常见坑和注意点

1. 修改字段失败(值接收者拷贝)

go 复制代码
func (u User) UpdateName(name string) {
    u.Name = name  // 修改了副本,不影响原值
}

2. 方法集与接口匹配不一致

go 复制代码
type Animal interface {
    Rename(string)
}
​
type Cat struct {
    Name string
}
​
func (c Cat) Rename(name string) { // 接收者是值
    c.Name = name
}
​
// ❌ 编译错误:Cat 没有实现 Rename(string)
var a Animal = Cat{}

解决方法:

go 复制代码
func (c *Cat) Rename(name string) {
    c.Name = name
}
​
var a Animal = &Cat{} // ✅ 用指针赋值

🧰 七、总结口诀(便于记忆)

💡「值传副本改不了,结构小且只读好;改值共享用指针,接口方法要对标。」

👉 立即点击链接,开启你的全栈开发之路:Golang全栈开发完整课程

相关推荐
十字路口的火丁2 小时前
在 Go 项目中如何使用 mockgen 提升单元测试效率?
go
DemonAvenger5 小时前
Go GOGC环境变量调优与实战案例
性能优化·架构·go
DemonAvenger8 小时前
Go sync.Pool 最佳实践:复用对象降低 GC 压力的技术文章
性能优化·架构·go
程序员爱钓鱼8 小时前
Go 并发编程基础:select 多路复用
后端·google·go
程序员麻辣烫9 小时前
Go的优雅退出
后端·go
zhuyasen10 小时前
深度定制 protoc-gen-go:实现结构体字段命名风格控制
后端·go·protobuf
油腻中年李大鹅1 天前
使用scheduler-plugins实现自定义调度器
kubernetes·go
DemonAvenger1 天前
减少内存分配:Go中值类型与指针类型的选择
性能优化·架构·go
Piper蛋窝1 天前
我所理解的 Go 的 `panic` / `defer` / `recover` 异常处理机制
后端·go
叹一曲当时只道是寻常1 天前
AI书签管理工具开发全记录(十三):TUI基本框架搭建
ui·go