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

相关推荐
研究司马懿20 小时前
【云原生】Gateway API高级功能
云原生·go·gateway·k8s·gateway api
梦想很大很大1 天前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
lekami_兰2 天前
MySQL 长事务:藏在业务里的性能 “隐形杀手”
数据库·mysql·go·长事务
却尘2 天前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
ん贤2 天前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
mtngt112 天前
AI DDD重构实践
go
Grassto4 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto6 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室7 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题7 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo