go函数参数全是值传递,包括slice、map、channel;它们修改元素有效因底层含指针,但扩容或重赋值需返回或传指针才能影响调用方。

Go函数参数全是值传递,包括slice、map、channel
Go语言里没有“引用传递”这个概念,所有函数调用都是值传递。但容易误解的是:像 slice、map、channel 这些类型传进去后,函数内修改元素似乎能影响原变量——其实是因为它们底层是结构体(含指针字段),复制的是结构体本身,而结构体里的指针仍指向同一块底层数组或哈希表。
例如 slice 底层是 struct { array unsafe.Pointer; len, cap int },传参时复制整个结构体,所以修改 s[i] = x 会生效,但 s = append(s, x) 可能分配新底层数组,此时原变量不受影响。
- 基本类型(
int、string、struct)传参后完全隔离,改不回原变量 -
slice修改已有索引元素有效;扩容后若底层数组变更,则不影响调用方 -
map和channel因内部含指针,增删改操作都反映到原值 - 想确保修改生效,且涉及重分配(如扩容、重建 map),必须显式返回并重新赋值
什么时候必须用指针传参
当函数需要修改调用方的变量本身(不只是其内容),就必须传指针。典型场景包括:
- 给一个空
struct字段赋值,且该 struct 没有导出字段或不可寻址(比如字面量) - 替换整个
slice底层数组(如reslice或 deep copy 后赋新地址) - 在函数内为
nil map或nil slice做make初始化 - 避免大结构体拷贝开销(比如几百字节以上的
struct)
示例:func initMap(m *map[string]int) { *m = make(map[string]int) } —— 不传指针的话,m = make(...) 只改本地副本,调用方仍是 nil。
立即学习“go语言免费学习笔记(深入)”;
string 是只读的,传参不会导致意外修改
string 在 Go 中是只读的底层字节数组 + 长度的结构体,传参时复制结构体,但因为不能修改其内容,所以既安全又高效。即使你把它当作“类似引用”的类型用,也不用担心被函数意外改写。
- 函数内对
string做s[0] = 'x'编译报错:cannot assign to s[0] - 拼接生成新字符串(
s + "x")会分配新内存,不影响原值 - 传
string几乎无性能负担,不用刻意传指针
容易踩坑的典型错误现象
最常见的误判是看到 append 没生效,就以为“Go 有时传引用有时传值”。其实只是没理解 append 的语义:它可能返回新 slice,而原变量未被更新。
- 错误写法:
append(s, x)单独调用,不接收返回值 → 原s不变,且新 slice 被丢弃 - 错误假设:
func f(m map[int]string) { m[1] = "x" }能清空或重置整个 map → 实际上可以改键值,但m = nil或m = make(...)对调用方无影响 - 混淆
len/cap行为:传入slice[:0]后在函数内append,可能复用原底层数组,也可能触发扩容,结果取决于容量余量
真正需要跨函数共享可变状态时,别依赖“传什么类型会自动同步”,老老实实传指针,或者用返回值显式覆盖。










