Go中所有参数都是值传递,传struct时修改副本不影响原变量;需用指针接收者才能修改原值;切片/map/channel是含指针的值类型;是否用指针取决于是否需修改、大小及接口实现。

为什么传 struct 时函数里改了字段,外面却没变?
因为 Go 所有参数都是值传递——func f(s MyStruct) 传进去的是整个 MyStruct 的副本,函数内修改的只是副本字段,原变量毫发无伤。
- 想让外部看到修改,必须用指针:
func f(s *MyStruct),然后在函数里写s.Field = "new" - 哪怕结构体只有两个
int字段,只要方法需要改状态(比如user.IncreaseLoginCount()),就该用指针接收者 - 常见错误现象:
user.SetName("Alice")调用后user.Name还是空字符串——十有八九接收者写成了func (u User) SetName(n string),而不是func (u *User) SetName(n string)
切片、map、channel 为啥“像引用”但又不是引用类型?
它们是「含指针的值类型」:变量本身是值(比如 sliceHeader 结构体),但内部字段存着底层数组/哈希表的地址。所以传参时虽拷贝 header,但底层数据仍共享。
-
append可能扩容并返回新 slice,原变量不变;而map["k"] = v直接生效,因 map header 里的指针指向同一 hash 表 - 误以为「map 是引用类型」会导致误判:把 map 当参数传进函数,函数里清空它(
clear(m)),调用方确实看到空了;但若函数里做了m = make(map[string]int),这只会改副本 header,不影响外层 - 安全做法:需要保证行为可预测时,显式传
*map或*[]T(虽然极少这么做)
nil 指针解引用 panic 和空接口赋值失败,根源都是同一套规则
Go 中一切皆值传递,但「值」的内容决定你能干什么:nil 是 *T 类型的零值,代表「地址为空」;而空接口 interface{} 存的是(类型, 数据)对,当值为 nil 时,类型信息可能丢失。
-
var p *string; fmt.Println(*p)→ 直接 panic:不能解引用nil指针 -
var s *string; var i interface{} = s; fmt.Println(i == nil)→ 输出false!因为i存的是 (*string,nil),类型非空,整体不等于nil - 判断指针是否有效,永远先做
if p != nil;判断接口是否为 nil,要拆成if i == nil || reflect.ValueOf(i).IsNil()(或更稳妥地用类型断言)
什么时候该用指针,什么时候死守值类型?
核心就看三件事:要不要改原值、对象大不大、接不接口。
立即学习“go语言免费学习笔记(深入)”;
- 要改原值 → 必须用指针(
*T参数或接收者) - 结构体字段总大小 > 16 字节(如含
[32]byte或多个string)→ 优先指针,避免复制开销 - 实现了某个接口?注意方法集:如果只有
func (t *T) Foo(),那T{}值本身无法赋给该接口变量,必须传&T{} - 小而只读的数据(
int、time.Time、轻量struct{ ID int; Name string })→ 传值更清晰,无 nil 风险,GC 压力小
最容易被忽略的一点:即使结构体很小,只要它和指针接收者的方法混用(比如标准库 sync.Mutex),就必须统一用指针——否则调用 Lock() 时传值会触发拷贝,锁的不是同一个实例。










