Go 语言:值传递 vs 指针传递

Go 语言:值传递 vs 指针传递

一、值传递 vs 指针传递

核心原则

默认用值传递,需要修改或避免拷贝开销时用指针。


用值传递的场景

go 复制代码
// 1. 基本类型(int、float、bool、string)--- 本身就很小
func add(a, b int) int { return a + b }

// 2. 小结构体(< 100 bytes),且不需要修改
type Point struct{ X, Y float64 }
func distance(p Point) float64 { ... }

// 3. 接口值 --- 底层数据已经是指针语义
func Process(r io.Reader) { ... }

用指针传递的场景

go 复制代码
// 1. 需要修改原始数据
type Counter struct { count int }
func (c *Counter) Increment() { c.count++ }

// 2. 大结构体 --- 避免拷贝开销
type BigConfig struct {
    Data    [4096]byte
    Options map[string]string
}
func Process(cfg *BigConfig) { ... }

// 3. 可能为 nil 的值
func Handle(res *http.Response) {
    if res == nil { return }
}

// 4. 一致性 --- 同类型方法接收者要统一,不要混用

关键对比

值传递 指针传递
修改原数据
拷贝开销 有(按大小) 无(8字节地址)
nil 安全 ✅ 零值可用 ❌ 需判空
GC 压力 无逃逸则栈分配 可能逃逸到堆
并发安全 ✅ 各自独立 ⚠️ 共享需加锁

实际建议

go 复制代码
// ❌ 不要这样 --- 基本类型传指针毫无意义
func add(a *int, b *int) int

// ❌ 不要为了"性能"对小结构体传指针 --- 逃逸到堆反而更慢
type Vec2 struct{ X, Y float64 }

// ✅ 粗略判断标准
// - ≤ 64 bytes 且不修改 → 值传递
// - > 64 bytes 或需要修改 → 指针传递
// - 方法接收者:一旦有一个方法用 *T,全部统一用 *T

方法接收者一致性

go 复制代码
type User struct {
    Name string
    Age  int
}

// ✅ 统一用指针 --- 因为有修改需求的方法
func (u *User) SetName(name string) { u.Name = name }
func (u *User) String() string      { return u.Name } // 虽然只读,也用 *User

// ❌ 混用 --- 编译能过但容易混乱
func (u *User) SetName(name string) { u.Name = name }
func (u User) String() string       { return u.Name }  // 类型不同:User vs *User

一句话总结:小且不改传值,大或要改传指针,方法接收者保持一致。


二、&* 详解

先理解"地址"这个概念

想象一间酒店:

  • 变量 = 房间里的东西(比如 302 房间住着一个人叫张三)
  • 地址 = 房间号(302)
  • 指针 = 一张写着房间号的小纸条

& --- "告诉我地址在哪"

go 复制代码
name := "张三"
p := &name

你问:张三住哪个房间?
&name 回答:302号

& 的作用就一个:找到这个东西住在哪,把地址给你。


* --- "我要看房间里的东西"

go 复制代码
name := "张三"
p := &name        // p 纸条上写着 302

fmt.Println(*p)   // 打印"张三"

你拿着写着 302 的纸条,推开 302 的门,看到里面是张三

* 的作用就一个:顺着地址,找到里面的东西。


可以修改!

go 复制代码
name := "张三"
p := &name

*p = "李四"  // 推开302的门,把里面的人换成李四

fmt.Println(name)  // 打印"李四" ------ 因为 name 就住在302

改了房间里的东西,原来的变量自然就变了。


放在一起对比

go 复制代码
x := 10

p := &x    // p = 小纸条,上面写着 x 的地址("x住哪")
*p         // 顺着纸条找到 x,读出 10("纸条指向谁")
*p = 20    // 顺着纸条找到 x,改成 20("去改纸条指向的人")

三种用法的对比

操作 符号 作用 示例
取地址 &x 获取 x 的指针 p := &x
解引用 *p 读写 p 指向的值 *p = 10
声明 *int 声明指针类型 var p *int

怎么记?

符号 一句话 记忆口诀
& 给我地址 & = address = 地址
* 顺着地址找内容 * = 指向里面的东西

常见模式

go 复制代码
// 1. 函数返回指针 --- 避免大对象拷贝
func NewUser(name string) *User {
    return &User{Name: name}  // & 取局部变量地址,Go 会逃逸到堆
}

// 2. 通过指针修改
func reset(val *int) { *val = 0 }

n := 99
reset(&n)   // n 变成 0
// reset(n)  // ❌ 编译错误:不能传 int 给 *int

// 3. 结构体指针可以直接访问字段 --- Go 自动解引用
u := &User{Name: "Tom"}
u.Name = "Jerry"     // 等价于 (*u).Name

常见错误

go 复制代码
var p *int        // 一张空纸条,上面什么都没写(nil)

fmt.Println(*p)  // ❌ panic!你拿着一张空纸条去找房间,找不到

空纸条不能推门,开门前先看看纸条是不是空的。

go 复制代码
x := 42
f(x)    // ❌ 编译错误:函数要的是纸条,你递过去一个真人
f(&x)   // ✅ 把地址(纸条)递过去

函数说要纸条你就给纸条,别直接把人塞过去。


最终总结:& 查地址,* 查内容。小且不改传值,大或要改传指针。

相关推荐
无风听海1 小时前
深入剖析 ASP.NET Core 的 UsePathBase
后端·asp.net
Allen_LVyingbo1 小时前
面向医疗群体智能的协同诊疗与群体决策支持系统(下)
开发语言·数据结构·windows·python·动态规划
读书札记20221 小时前
Qt Creator 调试报错:Unable to create a debugging engine.
开发语言·qt
透明的玻璃杯1 小时前
Qt Creator + Windows + Protobuf 最优方案(Mqqt通讯采用的方式)
开发语言·windows·qt
小书房1 小时前
Kotlin协程的运行原理
android·开发语言·kotlin·协程
隐退山林1 小时前
JavaEE进阶:SpringIoC&DI
java·开发语言·java-ee
水煮白菜王1 小时前
Claude Code 全方位使用手册
java·开发语言·网络
Highcharts.js1 小时前
金融Web App中的复杂时序数据可视化:从选型到高性能实践
开发语言·金融·highcharts·实战代码·响应式图表
郝学胜-神的一滴1 小时前
跨平台 C++ 静态库编译实战:Linux/Windows/macOS 三端统一实现
linux·开发语言·c++·windows·软件构建