<p>Go函数参数全是值传递,传指针能改原值是因为副本存的是同一内存地址;需传* T的场景包括修改调用方变量本身、避免大结构体复制、方法需更新字段、json.Unmarshal等要求可寻址值。</p>

Go里传指针和传值到底差在哪
Go没有引用传递,只有值传递——但传的是指针变量的值(即内存地址),所以能修改原数据。关键不是“用了*就一定改得到”,而是你传进去的那个变量本身是不是指向目标内存的地址。
常见错误:把结构体变量直接传给函数,函数里对字段赋值,调用后发现没变——因为传的是结构体副本;改成传&myStruct,函数参数声明为*MyStruct,才能生效。
- 基本类型(
int、string等)传指针才能被函数内修改 - 切片(
[]int)、映射(map[string]int)、通道(chan int)本身是引用类型,传值也能改底层数组/哈希表,但不能改变其长度容量或重新make分配——要改这些必须传指针 - 结构体是否传指针,取决于是否需要避免拷贝(大结构体)或是否需要修改原实例
什么时候必须用*T当参数,什么时候可以不用
看函数职责和性能需求。不是所有地方都得加星号,加错反而增加理解成本和空指针风险。
必须用*T的情况:
立即学习“go语言免费学习笔记(深入)”;
- 函数要修改调用方的变量值(比如
func swap(a, b *int)) - T是大结构体(>16字节常见阈值),避免每次调用都拷贝(如含多个字段的
type User struct { Name, Email string; Data []byte }) - 方法集需要实现某个接口,而该接口方法签名要求是指针接收者(例如
func (u *User) Save(),那调用u.Save()时u必须是指针)
可以且推荐用值传递的情况:
- T是小结构体(比如
type Point struct{ X, Y int }),传值更清晰、无nil风险 - 函数只读取字段,不修改,也不关心是否共享底层数据(如
func area(r Rectangle) float64) - 参数是
sync.Mutex这类不能拷贝的类型——编译器会报错,必须传*sync.Mutex
&和*在运行时出错的典型场景
最常踩的坑不是语法错,而是运行时 panic:panic: runtime error: invalid memory address or nil pointer dereference。这说明你用了*去解引用一个nil指针。
- 声明了指针变量但没初始化:
var p *int→p是nil,直接写*p = 42就崩 - 函数返回
nil指针但调用方没检查:u := findUser(123); fmt.Println(u.Name),而findUser找不到时返回nil - 切片/映射误当成指针用:
var m map[string]int; *m非法——map是引用类型,但不是指针,不能加*
安全做法:所有解引用前加判断,尤其来自外部输入或可选返回值:
if u != nil {
fmt.Println(u.Name)
}
结构体字段带*和不带*的区别
结构体里字段声明为*string而不是string,不是为了“支持nil”,而是为了区分“未设置”和“设为空字符串”。这是API设计常用技巧。
-
Name string:无法区分""是用户填了空,还是根本没填 -
Name *string:可明确表达三种状态——nil(未提供)、*s == ""(提供了空字符串)、*s == "foo"(提供了非空值) - JSON反序列化时,
*string字段对缺失字段保持nil,而string字段会变成""
代价是每次访问都要判空,且内存多一个指针开销。别为了“看起来灵活”全改成*T,只在语义确实需要三态时才用。
指针真正难的不是怎么写&和*,是怎么想清楚数据归属、生命周期和并发安全——比如两个 goroutine 同时写一个*int,不加锁就乱套。这点容易被忽略。


















