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 小时前
边界检查消除(BCE,Bound Check Elimination)
go
梁梁梁梁较瘦3 小时前
指针
go
梁梁梁梁较瘦3 小时前
内存申请
go
半枫荷3 小时前
七、Go语法基础(数组和切片)
go
梁梁梁梁较瘦20 小时前
Go工具链
go
半枫荷1 天前
六、Go语法基础(条件控制和循环控制)
go
半枫荷2 天前
五、Go语法基础(输入和输出)
go
小王在努力看博客2 天前
CMS配合闲时同步队列,这……
go
Anthony_49263 天前
逻辑清晰地梳理Golang Context
后端·go
Dobby_054 天前
【Go】C++ 转 Go 第(二)天:变量、常量、函数与init函数
vscode·golang·go