go中所有函数参数均为值传递,即传递变量的副本:基本类型传值拷贝,slice/map/chan/*t传header或地址拷贝,修改是否生效取决于操作对象是副本还是其指向的同一底层数据。

Go 里所有函数参数都是值传递
结论很直接:Go 没有“传引用”这回事,func 参数永远是值传递。所谓“传指针”,本质还是把指针变量的值(即内存地址)拷贝了一份传进去——它仍然是值传递,只是这个值恰好是个地址。
常见错误现象:append 后原切片没变、struct 字段修改不生效、以为 *T 就能“改变实参本身”。其实问题不在“传什么”,而在“你改的是谁的副本”。
- 基本类型(
int、string、bool):传的是值的完整拷贝,函数内改不影响外部 -
slice:传的是slice header的拷贝(含ptr、len、cap),所以能改底层数组内容,但append可能导致扩容并生成新 header,原变量不变 -
map和chan:底层是引用类型结构体指针,header 拷贝后仍指向同一底层数据,所以增删改都反映到原变量 -
*T:传的是指针值(地址)的拷贝,解引用后修改的是同一块内存,效果等同于“可修改原值”
什么时候必须传 *T 而不是 T
不是为了“性能”,而是为了“可修改性”或“避免拷贝大对象”。Go 编译器不会帮你判断要不要加星号,全靠你明确意图。
使用场景:struct 字段需要被函数修改;结构体很大(比如含大数组或大量字段);实现接口方法时接收者需可变(如 bytes.Buffer.Write)。
- 如果只读,传
T更安全,也更易推理 - 如果要改字段,必须传
*T,否则改的是副本字段,外部看不到 - 传
*T不等于“函数能改T的地址”,你改的是*T指向的内容,T变量本身的地址始终不变 - 注意空指针:传
nil的*T进去,解引用前没判空会 panic:panic: runtime error: invalid memory address or nil pointer dereference
slice 作为参数时的典型陷阱
slice 是最常让人误以为“传引用”的类型。它表现得像引用,但行为受 header 拷贝和底层数组共享共同影响,边界模糊。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
常见错误现象:函数里 append 后原 slice 长度/容量没变、元素没新增;或者以为 s = append(s, x) 能让调用方看到新 slice,结果发现没生效。
-
append不修改原 header,而是返回新 header;若底层数组有足够 cap,新 header 的ptr不变,但len变了;若扩容,ptr也变,原变量完全不受影响 - 想让调用方拿到追加后的 slice,必须显式返回:
newS := append(s, x),然后由调用方赋值 - 想在函数内就地修改底层数组内容(比如
s[0] = 1),不需要返回,因为ptr相同,改的是同一块内存 - 别依赖
len(s) == cap(s)来判断是否扩容,这是不可靠的实现细节;要用len(newS) > len(oldS)判断是否发生 realloc
接口类型参数的传递行为
接口值本身是两字宽结构体(type + data),传参时整个结构体被拷贝。但它的“语义”容易混淆:它既不是纯值,也不是纯引用。
使用场景:写通用函数(如日志、序列化)、依赖注入、mock 测试。关键在于理解拷贝的是接口头,而 data 部分可能是指针或值。
- 如果接口底层是
*T,那拷贝的是指针值,解引用后仍指向原内存 → 修改可见 - 如果接口底层是
T(比如var i interface{} = struct{X int}{1}),那拷贝的是整个 struct 值 → 修改不可见 - 接口方法调用时,接收者是值还是指针,决定了能否修改底层数据;这和普通函数参数规则一致,只是多了一层间接
- 不要假设
fmt.Stringer或error接口传入后就能改原始对象;要看具体实现类型用的是值接收者还是指针接收者
最容易被忽略的一点:Go 的“值传递”是彻底的,连 map 和 chan 也是——它们只是 header 里存了指向共享数据结构的指针。真正共享的是底层 hmap / hchan,不是变量本身。理解这一点,才能不被表象带偏。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










