go所有参数均为值传递,指针传的是地址副本;需修改原值或避免大结构体拷贝时才用* t;slice/map/channel传值可改内容但不能替换本身;取地址与解引用不可混淆,nil检查必不可少。

Go 里根本没有“传引用”,只有传地址的值
结论很直接:Go 所有函数参数都是值传递,*int、*string、*User 这些指针类型传进去的,只是那个内存地址(比如 0xc000010240)的副本——8 字节拷贝而已。它不是 C++ 的 int&,也不是 Java 的对象引用语义。
为什么这么设计?因为 Go 要保持内存模型简单、可预测。你拿到的是“能访问原数据的钥匙”,但钥匙本身是复制的。
- 修改
*p指向的值(如*p = 42),原变量会变 —— 因为两把钥匙开同一扇门 - 让指针变量自己指向别处(如
p = &x),不影响调用方的指针 —— 你换了一把新钥匙,旧钥匙还在别人手里 -
nil指针解引用会 panic:panic: runtime error: invalid memory address or nil pointer dereference,这是最常踩的坑
什么时候必须传 *T,而不是 T
两个核心场景:要改原值,或结构体太大。
基本类型(int、string)传值开销小,一般没必要用指针;但结构体哪怕只有几个字段,一旦嵌套深或含 slice/map,拷贝成本就明显了。
- 要修改原始变量:比如
func increment(x *int) { *x++ },调用increment(&a)才能让a真的加 1 - 避免大结构体拷贝:一个含 10KB JSON 字段的
type Config struct,传Config是复制 10KB,传*Config只复制 8 字节地址 - 方法接收者需修改字段时,必须用指针:如
func (u *User) SetName(n string) { u.name = n },否则u是副本,改了白改
& 和 * 到底在干啥:取地址 ≠ 解引用
新手最容易混淆这两个操作符的作用对象和时机。
&v 是“我要拿到 v 存在哪”,返回一个 *T 类型值;*p 是“我要打开 p 指向的地方”,返回 T 类型值。它们成对出现,但不能乱套。
- 不能对字面量取地址:
&42报错:cannot take the address of 42;同理&"hello"也不行 - 不能对临时表达式取地址:
&strings.ToUpper(s)错误,因为ToUpper返回的是新字符串,没固定内存位置 - 解引用前务必检查是否为
nil:比如if p != nil { fmt.Println(*p) },否则运行时 panic
和 slice/map/channel 混用时的常见误解
很多人发现 func modifySlice(s []int) { s[0] = 999 } 能改原 slice 元素,就以为“slice 是引用传递”——其实不是。slice 本身是三字段结构体(ptr, len, cap),传的是这个结构体的副本;副本里的 ptr 和原 slice 指向同一底层数组,所以改元素生效;但若在函数里 s = append(s, 1),可能触发扩容,新数组和原数组就分家了,原 slice 不受影响。
- 想确保扩容也影响原变量?得传
*[]int,即指针的指针:func appendSafely(s *[]int, x int) { *s = append(*s, x) } - map 和 channel 同理:传值即可修改内部键值或收发行为,但无法用
m = make(map[int]int)替换原 map 变量本身 - 别试图对 map/slice 取地址:
&m编译报错:cannot take the address of m,因为它们是不可寻址的值类型
真正容易被忽略的,是“指针的生命周期”问题:局部变量的地址不能返回,比如函数里 return &x(x 是栈上变量),Go 编译器会自动逃逸分析并分配到堆,但逻辑上你要清楚谁在管理这块内存 —— 它不会因为你没写 new 就消失。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











