go函数参数全是值传递,所谓“指针传递”只是传地址值;int存地址(如0xc00001a080),解引用修改的是该地址指向的原始内存,而int传的是数值副本,修改仅影响副本;结构体传t或t取决于是否需修改字段、大小是否超阈值(如≥64字节)、是否满足接口方法集要求;slice/map/chan是含指针的值类型,传值可改内容但不能替换头结构。

Go函数参数全是值传递,所谓“指针传递”只是你传了一个地址值——它和传int、string一样,都是复制一份再进函数;区别只在于你塞进去的是数据本身,还是指向数据的地址。
为什么func f(x *int)能改原变量,但func f(x int)不能
因为*int类型变量存的是内存地址(比如0xc00001a080),函数拿到的是这个地址的副本;解引用*x = 100时,写入的是该地址指向的原始内存位置。而int传进来的是数值副本(比如42),改它只动副本。
- 误以为“指针传递=引用传递”是最大认知陷阱——两个
*int变量可以指向同一块内存,但它们自己是独立变量,各自在栈上占8字节 -
&x取地址操作必须作用于可寻址值:局部变量、结构体字段、切片元素可以;const、字面量、函数返回值(除非是可寻址的)不行 - 编译器不会阻止你传
&42这种非法操作,但会直接报错:cannot take the address of 42
结构体传T还是传*T?看三个硬指标
别凭感觉加星号。是否用指针,取决于这三条是否有一条成立:
- 需要修改调用方结构体字段:比如
func (u *User) SetName(n string),用func (u User)改u.Name等于白干 - 结构体大小超过几个字节(实测:≥64字节就该警惕):一个含
[]byte或嵌套map的User可能几百KB,值传一次就是一次堆分配+GC压力 - 方法集要求:如果接口方法接收者是
*T,那只有*T能赋给该接口;T无法满足,哪怕它也有同名方法
小结构体如type Vec2 struct{ X, Y float64 }(16字节),值传更轻量、无nil风险、逃逸分析更友好。
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
slice、map、chan为什么传值也能改内容
它们不是“引用类型”,而是**含指针的值类型**:传参时复制的是头结构(slice是ptr/len/cap三字段,map是hmap*指针)。所以:
-
s[0] = 999能改底层数组,因为ptr字段没变 -
s = append(s, x)可能触发扩容,新数组地址写进副本的ptr,原s完全感知不到——这不是bug,是设计使然 - 传
*[]int或*map[string]int纯属冗余:多一层解引用、多一次nil检查、不idiomatic,除非你真要替换整个头结构(极少见)
最容易被忽略的底层细节:逃逸与接口实现
传&localVar可能让本该在栈上的变量逃逸到堆——尤其当该指针被返回、传入goroutine、或存进全局map时。编译器会静默升级分配位置,但GC压力会上升。
接口赋值时,var i fmt.Stringer = u和var i fmt.Stringer = &u底层存储完全不同:前者存u的副本,后者存指向u的指针。如果u.String()是func (u *User) String(),那u(值)根本不能赋给fmt.Stringer,编译失败。
真正难的不是语法,而是每次写&前,得想清楚:这个地址会不会被长期持有?外部有没有可能把它当成可修改入口?它指向的东西生命周期是否足够长?
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










