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)   // ✅ 把地址(纸条)递过去

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


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

相关推荐
陈随易15 小时前
VSCode的Copilot扩展支持接入DeepSeek,Kimi了!
前端·后端·程序员
我不是外星人16 小时前
有了 Harness Engineering ,真的还需要研发工程师吗?
前端·后端·ai编程
candyTong16 小时前
RTK 技术原理:一次典型会话里,80% 上下文是怎么省下来的
javascript·后端·架构
Rust研习社18 小时前
组合真的优于继承吗?为什么 Rust 和 Go 都拥抱组合舍弃继承?
后端·rust·编程语言
IT_陈寒19 小时前
JavaScript的闭包把我坑惨了,说好的内存会自动回收呢?
前端·人工智能·后端
CaffeinePro20 小时前
Pydantic深度使用:数据校验、枚举、ORM映射
后端·fastapi
Chenyiax20 小时前
从 Chat 到 Responses:OpenAI API 抽象为什么变了?
后端
MariaH20 小时前
Koa和Express的区别
后端
MariaH20 小时前
Koa框架的使用
后端
luckdewei21 小时前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端