go语言函数参数全是值传递,slice修改元素生效是因为header中指针副本仍指向原底层数组,而append返回新header且未赋值回原变量,故调用方无感知;map和chan同理,底层为指针类型,增删改操作作用于共享结构,但重新make不影响原变量。

为什么 slice 修改元素却生效,但 append 却不生效?
因为 slice 是 header(含 ptr、len、cap)的值拷贝,函数内拿到的是同一底层数组的指针副本:
-
s[0] = 1改的是底层数组,所以调用方能看到变化 -
s = append(s, x)是重新赋值给形参s,只改了 header 副本,原变量的ptr/len没变 - 扩容时
append返回新 header,旧变量完全无感知
想让调用方拿到追加后的结果,必须显式返回并重新赋值:s = modifySlice(s)。
map 和 chan 为什么“看起来像引用传递”?
它们底层是结构体指针(*hmap / *hchan),传参时拷贝的是这个指针值,所以增删改都作用于同一底层数据:
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
-
m["k"] = v、delete(m, "k")、close(c)都会反映到原变量 - 但
m = make(map[int]int)或c = make(chan int)不会影响调用方——你只是改了指针副本的指向 - 和
slice一样,它们不是“引用类型”,只是“值为指针的类型”
什么时候必须传 *T 而不是 T?
不是为了性能,而是为了修改原始值或避免大对象拷贝:
- 结构体字段要被函数修改:传
*Person,否则p.Name = "x"只改副本 - 结构体很大(比如含 1MB 数组):传
*BigStruct避免栈上拷贝开销 - 实现接口方法时需可变状态:如
bytes.Buffer.Write必须用指针接收者 - 空指针风险:传
nil的*T进去,解引用前没判空会 panic:panic: runtime error: invalid memory address or nil pointer dereference
int、string、struct 传参后修改无效的常见误判
这类值类型传的是完整副本,函数内任何赋值都不影响外部:
-
func f(s string) { s = "new" }→ 原s不变,因为string是只读 header + 底层数组指针,赋值只是换 header -
func f(p Person) { p.Age++ }→ 外部Person字段不变 - 别指望靠传值来“保护”大结构体:如果只读,传
T更安全;如果要改,不加*就注定失效
最易被忽略的点:所有类型统一按“值传递”执行,区别只在于“这个值本身是不是地址”。把 slice 当引用、把 *T 当引用传递,都是对底层机制的误读——你始终在操作副本,只是副本有时恰好指向同一块内存。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










