go函数参数全是值传递,传指针能改原值是因为副本存的是同一内存地址,而非语言支持引用;需传* t 的场景包括修改调用方变量本身、大结构体避免复制、方法需更新字段、json.unmarshal等要求可寻址值。

Go函数参数全是值传递,别被“指针能改原值”骗了
Go里没有引用传递,func f(x *int) 和 func f(x int) 都是值传递——区别只在于你塞进函数的是整数 42,还是地址 0xc00001a080。传指针之所以能改外部变量,是因为副本里存的是同一块内存的地址,不是因为“语言支持引用”。误以为 Go 有引用语义,是绝大多数 panic 和逻辑 bug 的源头。
什么时候必须传 *T,而不是 T
不是“想改就加星号”,而是不加就做不到:
- 需要修改调用方变量本身:比如
swap(a, b *int),不传指针根本没法交换两个变量的值 - 结构体字段多、含大
[]byte、map[string]int或嵌套 struct:值传会复制整个 header + 字段值,bench 能明显看到runtime.mallocgc上升 - 方法接收者要更新字段:
func (u *User) SetName(n string)是标准写法;用func (u User)写,u.Name = n只改副本,外部不变 - 像
json.Unmarshal这类函数要求v interface{}实际指向可寻址值,传&v才生效,传v会静默失败或 panic
传 *T 容易踩的三个坑
传指针不是万能钥匙,搞错就 crash 或逻辑错:
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
-
nil *T解引用必 panic:var p *string; fmt.Println(*p)直接终止程序;调用前得判空,或用if p != nil - 栈变量取地址后逃逸:局部变量
s := MyStruct{...},传&s可能导致它从栈挪到堆,GC 压力上升;小 struct(如type Vec2 struct{ X, Y float64 })值传更高效 - 并发读写没加锁:多个 goroutine 同时通过
*T修改同一结构体字段,不加sync.Mutex或原子操作,结果不可预测
slice、map、chan 为什么不用 * 也能改内容
它们不是“引用类型”,而是**含指针的值类型**。传 slice 时复制的是它的 header(ptr、len、cap 三字段),其中 ptr 指向底层数组。所以:
-
append(s, x)可能分配新数组,这时原s不变,得用返回值:s = append(s, x) -
s[0] = 999能改原数组内容,因为ptr没变 -
map同理:m["k"] = v改的是底层 hash 表,但m = make(map[string]int)只改副本,不影响外部
真正容易忽略的是:这些类型传值已有共享语义,再套一层 *[]T 或 *map[K]V 不仅多余,还增加解引用开销和 nil 判空负担。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










