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全栈开发完整课程

相关推荐
我的golang之路果然有问题4 小时前
快速上手GO的net/http包,个人学习笔记
笔记·后端·学习·http·golang·go·net
用户16849371443115 小时前
通过 goat 工具对 golang 应用进行增量代码的埋点和监控
go
旅人CS5 小时前
用Go语言理解单例设计模式
设计模式·go
用户0142260029845 小时前
Go(Golang)类型断言
go
纪元A梦8 小时前
华为OD机试真题——通过软盘拷贝文件(2025A卷:200分)Java/python/JavaScript/C++/C语言/GO六种最佳实现
java·javascript·c++·python·华为od·go·华为od机试题
孔令飞12 小时前
彻底学会 gRPC:用 Go 实现一个迷你考试服务
人工智能·云原生·go
GetcharZp14 小时前
FileBrowser:用浏览器轻松管理服务器文件,简洁又强大
后端·go
帽儿山的枪手1 天前
如何使用socket系统调用创建TCP三次握手呢?
网络协议·tcp/ip·go
行者无疆xcc1 天前
【Go】重难点知识汇总
go