函数传参能否修改原变量取决于是否传递指针:值类型(如int、string、小struct)传参是副本,修改无效;指针类型(如int、user)可修改原变量;切片、map、channel虽含指针,但传参仍是值传递,需传指针才能修改其本身。

函数传参时改不改得了原变量?看传的是值还是地址
Go 里没有“引用传递”,只有“值传递”——但你传的这个“值”,可能是数字 42,也可能是地址 0xc00001a240。关键就在这一步:想改原始变量,必须传指针。
- 传
int、string、User(小 struct)这类值类型,函数内改参数 = 白改,调用方完全无感 - 传
*int、*User,函数里用*p = 100就真把原变量改了 - 常见错误:
modify(u User)里写了u.Name = "x",结果外面还是旧值——因为 u 是副本 - 正确写法:
modify(&u)配合func modify(u *User) { u.Name = "x" }
结构体方法该用值接收者还是指针接收者?
不是看“顺不顺眼”,而是看三点:要不要改字段、结构体大不大、接不接口。
- 要改字段(比如
SetAge、Reset)→ 必须用*User接收者,否则改的只是副本 - 结构体字段多或含大数组(如
[1024]byte)→ 即使只读,也建议用指针接收者,避免每次调方法都复制一遍 - 要实现某个接口(比如
fmt.Stringer),而该接口的方法是用指针接收者定义的 → 只有*User能赋值给接口变量,User{}会编译报错 - 混用值/指针接收者容易翻车:一个方法用
User,另一个用*User,可能导致部分实例无法满足同一接口
切片、map、channel 是“引用类型”?别被名字骗了
它们底层确实含指针,但传参仍是值传递——传的是包含指针的“结构体副本”。这导致行为很微妙:
-
func appendItem(s []int, x int) { s = append(s, x) }→ 外面的s不变,因为s本身被复制了,append改的是副本的底层数组指针 - 想真正追加?得传指针:
func appendItem(s *[]int, x int) { *s = append(*s, x) } -
map和channel同理:能改内部元素(m["k"] = v、ch ),但不能改其本身(比如让函数内 <code>m = make(map[int]int)不会影响外面) - 误以为“map 是引用类型所以传进去就能重赋值”,是 Go 新手最常踩的逻辑坑
nil 指针解引用和逃逸分析:两个安静但致命的问题
指针用不对,程序不会立刻报错,但会在运行时 panic 或悄悄变慢。
立即学习“go语言免费学习笔记(深入)”;
-
var p *int; fmt.Println(*p)→ 直接 panic: "invalid memory address or nil pointer dereference" - 传指针前务必判空,尤其从 map 查值、JSON 解析、数据库查询返回指针字段时
- 频繁对局部变量取地址并返回(如
return &x),会让本该在栈上的小对象逃逸到堆,GC 压力上升——用go build -gcflags="-m"可观察 - 不是“用了指针就一定上堆”,但
&+return是最常见逃逸触发器
真正难的不是语法,而是每次写 func f(x T) 还是 func f(x *T) 时,脑子里要同时过三件事:这次调用需不需要改原值?这个结构体拷起来贵不贵?它以后会不会被塞进某个接口变量里?漏掉任意一条,都可能埋下 bug 或性能隐患。










