go只有值传递和指针传递,所谓“引用类型”如slice、map本质是含指针的结构体,传参仍是值传递;修改基础类型或避免大结构体拷贝等场景必须用*T。

Go 里没有“引用传递”,只有值传递和指针传递
Go 语言中不存在 c++ 那种语法层面的引用类型(&T 作为独立类型),所有函数参数都是值传递。所谓“引用类型”(如 slice、map、chan、func、*T)只是底层数据结构包含指针字段,使得它们在传参时能“看起来像”可修改原数据——但这本质仍是值传递一个含指针的结构体。
真正想修改调用方变量的值,必须显式传入指针:*T。比如想让函数改掉一个 int 变量,不传 *int 就永远改不了。
什么时候必须用 *T 传参
以下情况需传指针,否则无法影响原始变量:
- 修改基础类型(
int、String、Struct等)的字段或值 - 避免大结构体拷贝(例如几百字节以上的
struct),提升性能 - 实现接口时,方法集与接收者类型强相关:带指针接收者的方法只能被
*T调用
示例:
立即学习“go语言免费学习笔记(深入)”;
func increment(x *int) { *x++ } n := 42 increment(&n) // 必须取地址;传 n 无效 // n 现在是 43
slice、map 为什么“不用指针也能改”
因为它们本身是头信息结构体,内部含指针字段:
-
slice是struct{ ptr *T; len, cap int }—— 传参复制的是这个结构体,但ptr指向同一底层数组 -
map是*hmap(即指针类型),所以传map[K]V实际上传的是指针的副本
注意:这不等于“安全”。以下操作仍不会影响原变量:
- 对
slice重新赋值(s = append(s, x)可能导致扩容,生成新底层数组) - 对
map重新赋值(m = make(map[int]string)只改副本)
若需保证扩容后仍影响原 slice,必须传 *[]T;map 同理需 *map[K]V(极少用,通常说明设计有问题)。
常见误用和坑
容易混淆的点集中在“什么变了、什么没变”:
- 传
struct{ name string; age int }→ 修改字段无效;传*struct{...}→ 有效 - 传
[]byte→ 可改元素、可append(但扩容后原 slice 不变);传*[]byte→ 连底层数组更换都能同步 - nil 指针解引用会 panic:
var p *int; *p = 1→ crash。使用前务必检查是否为 nil - 返回局部变量地址安全(Go 编译器自动栈逃逸分析),但返回局部数组的地址要小心:如
return &[3]int{1,2,3}安全,arr := [3]int{1,2,3}; return &arr也安全(逃逸),但语义易误导
最常被忽略的是:函数签名是否匹配方法集。比如定义了 func (t *User) Save(),却用 User{} 值调用,会报错 cannot call pointer method on ...。