Go函数参数全是值传递;slice、map、chan因内部含指针,拷贝后仍指向同一底层数组或结构,故修改可见,但扩容后slice会指向新数组。

Go 函数参数全是值传递,没有引用传递——这是确定结论,不是“看情况”。面试时如果还说“slice 是引用传递”,基本等于没理解底层机制。
为什么 slice 在函数里改元素,外面也变了?
因为 slice 本身是值类型,但它的底层结构包含三个字段:array(指针)、len、cap。传参时这三者被整体拷贝,其中 array 指针值被复制了一份,但它仍指向同一块底层数组。
- 只要不触发扩容(比如只读、或
append未超cap),所有基于该slice的修改都作用于原数组 - 一旦
append导致扩容,就会新建底层数组,函数内slice的array指针会指向新地址,此时修改不影响原slice -
&s打印的地址是slice变量自身的栈地址,和底层数组无关,不能用来判断是否“共享”
map 和 chan 为什么也能被修改?
它们和 slice 类似:变量本身是值类型,但内部持有指向底层数据结构(hmap / hchan)的指针。传参拷贝的是这个指针值,不是整个结构体。
-
map的底层是哈希表,chan是环形缓冲区 + 锁 + 等待队列,都分配在堆上 - 所以函数内调用
m["k"] = v或ch ,实际是通过指针去操作堆上的数据 - 但注意:
make(map[int]int, 10)返回的仍是值,只是这个值里存着堆地址
怎么一眼验证是不是真“传值”?
直接打印变量地址:对基础类型(如 int)取地址,函数内外地址不同;对指针类型(如 *int)取地址,函数内外变量自身地址不同,但解引用后地址相同。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 写个
modifyInt(i int),在函数里fmt.Printf("%p", &i),对比main里&i—— 地址不同,证明是副本 - 写个
modifyPtr(p *int),在函数里fmt.Printf("%p", p)(不是&p),对比main里p的值 —— 地址相同,说明指针值被拷贝了,但指向没变 - 别信
fmt.Println(s)输出一样就以为“没拷贝”,要抓底层行为
面试最容易踩的坑
把“能修改原数据”等同于“引用传递”,或者认为“只有指针才是值传递”。其实关键不在语法糖,而在编译器看到的实参类型和它如何生成指令。
- 所有参数都是拷贝:
int拷贝 8 字节,[]int拷贝 24 字节(ptr+len+cap),map拷贝 8 字节(指向 hmap 的指针) - 逃逸分析里出现
escapes to heap不代表“传引用”,只说明变量生命周期超出栈帧,必须堆分配 —— 即使是return &x这种纯值类型,也得逃 - 闭包捕获变量、
interface{}赋值、goroutine 捕获局部变量,这些都会强制逃逸,但和传参方式无关
真正难的不是记住“都是值传递”,而是理解:值拷贝了什么、拷贝的东西指向哪里、运行时怎么调度那块内存。面试官追问细节时,卡在“底层结构”和“逃逸路径”上,比背概念管用得多。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










