Go中只有值传递,&x取地址得T类型值,p解引用读取指向的值;传指针本质是传地址值副本,修改指针本身不影响原变量。

Go 里 & 和 * 到底在干啥
Go 中没有“引用传递”,只有值传递;但你可以传一个指针类型的值,它本身存的是地址。所以关键不是“传什么”,而是“传进去的东西是什么类型”。&x 取出变量 x 的内存地址,得到一个 *T 类型的值;*p 是解引用,从地址 p 读出它指向的 T 类型的值。
常见错误是以为写了 func f(p *int) { p = &y } 就能改外面的指针变量本身——其实不能,因为 p 是 *int 的副本,你只是把它内部存的地址换掉了,原变量没变。
-
&x的结果是一个新生成的指针值(地址),类型为*T -
*p操作需要p != nil,否则 panic:invalid memory address or nil pointer dereference - 结构体字面量里用
&S{...}得到的是指向新分配结构体的指针,不是对已有变量取地址
函数参数传 *T 还是 T?看这三点
决定是否用指针传参,不看“要不要修改”,而看三件事:数据大小、是否真要修改原值、API 一致性。
- 小类型(如
int、bool、[3]int)传值开销小,优先传值;大结构体(比如含[]byte或几十字段)传指针避免拷贝 - 如果函数逻辑上“必须修改调用方状态”,比如
json.Unmarshal(&v),那就得传*T;否则传值更安全、更易测试 - 同个类型的方法集最好统一:如果已经有
(*T).Method(),那其他函数也建议接收*T,避免混用造成理解成本
type User struct {
Name string
Age int
}
func updateUser(u *User) { // 明确表示会改 u 指向的值
u.Name = "Alice"
}
func copyUser(u User) User { // 不动原值,返回新副本
u.Name = "Bob"
return u
}
切片、map、channel 为什么“像引用”但仍是值传递
它们底层都是结构体(header),包含指针字段。例如 slice 实际是 struct{ ptr *elem, len, cap }。传 slice 时,这个 header 被完整拷贝,但其中的 ptr 字段仍指向同一块底层数组——所以改元素会反映到原 slice,但做 s = append(s, x) 可能导致扩容,新 header 的 ptr 就指向别处了,原 slice 不受影响。
立即学习“go语言免费学习笔记(深入)”;
-
map和channel同理:header 拷贝,内部指针共享;但make(map[int]int)返回的是 header 值,不是指针 - 想让函数彻底控制底层数组(比如强制扩容并让调用方看到),必须传
*[]T,即指向切片 header 的指针 - 常见坑:
func badAppend(s []int) { s = append(s, 99) }—— 外面的 slice 完全不受影响
func goodAppend(s *[]int) {
*s = append(*s, 99)
}
nums := []int{1, 2}
goodAppend(&nums) // nums 现在是 [1 2 99]
什么时候必须用 new(T) 或 &T{}
new(T) 返回 *T,且把内存清零;&T{} 也返回 *T,但按字段顺序初始化(未写的字段仍为零值)。二者都不常用,多数时候直接写 &User{Name: "X"} 更清晰。
-
new(T)唯一不可替代场景:你需要一个零值指针,但T没有字面量语法(比如接口、函数类型)——但这种情况极少 -
&T{}常用于初始化结构体,尤其字段多或含嵌套时;注意&struct{X int}{1}合法,但struct{X int}{1}是值,不是地址 - 不要为了“统一风格”而滥用:
var x *int; x = new(int)不如x := new(int)直观,更不如var x int; px := &x明确
*T 赋给 interface{},传进去的仍是那个指针值的副本——但它所指的地址没变,所以通过 interface{} 调用方法仍能修改原值。这点让很多人误以为 interface{} 是引用传递。










