&只能取已有变量左值的地址,字面量和函数调用结果无固定内存地址,故不可取址;nil指针解引用会panic;传指针核心目的是修改原值而非省拷贝;循环中取局部变量地址会导致所有指针指向同一内存位置。

为什么 & 不能直接作用于字面量或函数调用结果
因为 & 只能取**已有变量左值**的地址,而字面量(如 42、"hello")和表达式(如 len(s)、s[0] + 1)没有固定内存地址,编译器无法为其分配可寻址位置。
- ✅ 合法:
x := 42; p := &x——x是变量,有确定栈/堆地址 - ❌ 编译报错:
p := &42或p := &(fmt.Sprintf("a"))—— 错误信息是cannot take the address of ... - ⚠️ 特例:
&s[0]合法(slice 元素地址稳定),但&m["k"]非法(map 值地址不保证,编译失败)
解引用前不判 nil 就 panic
* 操作对 nil 指针是未定义行为,Go 不做防护,直接触发 runtime error: invalid memory address or nil pointer dereference。
- 函数参数为
*T类型时,第一行应检查:if p == nil { return } - JSON 反序列化后结构体字段若为
*string,访问前必须写:if u.Name != nil { fmt.Println(*u.Name) } - 接口变量存了
nil指针(如var u *User; i := interface{}(u)),i == nil为 false —— 此时需用reflect.ValueOf(i).IsNil()判断,但更推荐设计上避免这种赋值
传指针不是为了省拷贝,而是为了改原值
很多人误以为传 *T 主要是为了避免大对象拷贝。实际上,只要函数只读不写,传值更安全清晰;真正需要指针,是当函数内部必须修改调用方原始变量时。
- ✅ 必须传指针:
func updateUser(u *User) { u.Name = "Alice" }—— 外部User实例被改 - ❌ 无需指针:
func printUser(u User) { fmt.Println(u.Name) }—— 传值即可,小结构体还有编译器优化 - ⚠️ 切片是引用类型,但扩容会影响底层数组:若需让调用方看到新底层数组,必须传
*[]T,否则扩容只影响副本
循环中取局部变量地址导致所有指针指向同一值
for 循环里反复声明同名变量并取其地址,Go 会复用栈空间,导致生成的多个指针实际都指向最后一次迭代的内存位置。
立即学习“go语言免费学习笔记(深入)”;
- ❌ 危险写法:
for _, name := range data { p := Person{Name: name}; people = append(people, &p) }——people所有元素地址相同 - ✅ 正确做法:
people = append(people, &Person{Name: name})—— 每次构造新结构体并分配在堆上 - 验证方式:打印每个指针地址 ——
fmt.Printf("%p\n", people[i]),若全一样就是踩坑了
指针真正的复杂点不在语法,而在生命周期与所有权的隐含契约:谁分配、谁释放(GC 管)、谁负责判空、谁保证地址有效。这些不写在代码里,却决定程序是否 crash 或静默出错。


















