必须用 解引用才能修改原变量值,指针本身只是地址,不加 操作的是指针变量自身;传指针进函数需用 *p 修改目标值,nil 指针解引用会 panic,须判空;结构体嵌套指针字段需逐层检查并初始化;new(T) 仅得零值指针,&T{} 支持字段初始化;返回局部变量地址安全,因逃逸分析自动移至堆。

用 * 解引用才能改原变量的值
Go 里指针本身只是个地址值,不加 * 就是在操作指针变量自己(比如赋新地址),不是在改它指向的值。常见错误是传指针进去却忘了 *,结果函数里看似“改了”,实际原变量毫发无伤。
实操要点:
- 声明指针用
&,修改目标值必须用*前缀 - 函数参数接收
*int,内部要写*p = 42才能改调用方的int - 对
nil指针解引用会 panic,操作前建议判空(尤其从 map 或函数返回获取的指针)
func increment(p *int) {
if p != nil { // 防 panic
*p++
}
}
x := 10
increment(&x)
// x 现在是 11
结构体指针字段赋值要注意是否已初始化
结构体指针字段(如 type User struct{ Profile *Profile })默认是 nil。直接写 u.Profile.Name = "Alice" 会 panic:assignment to entry in nil map / invalid memory address。
正确做法:
立即学习“go语言免费学习笔记(深入)”;
- 先检查
u.Profile != nil,再赋值 - 或提前初始化:
u.Profile = &Profile{} - 如果字段是嵌套指针(如
*map[string]int),需逐层判空 + 初始化
type Config struct {
DB *DBConfig
}
c := &Config{}
if c.DB == nil {
c.DB = &DBConfig{Host: "localhost"}
}
c.DB.Port = 5432 // 安全
new() 和 &T{} 初始化指针的区别
两者都返回指向新分配零值的指针,但语义和适用场景不同:
-
new(T)只能创建零值,返回*T,适合简单类型或明确需要零值的场景 -
&T{Field: val}支持字段初始化,更常用,尤其结构体 - 切片、map、channel 不能用
new()初始化(它们本身是引用类型,new([]int)返回的是*[]int,不是你想要的可 append 的切片)
// ✅ 正确
p1 := new(int) // *int,值为 0
p2 := &User{Name: "Tom"} // *User,Name 已赋值
// ❌ 错误用法
s := new([]string) // 得到 **[]string,不是能 append 的切片
// 应该用:
s := make([]string, 0)
函数返回局部变量地址是安全的
Go 编译器会自动做逃逸分析,把本该分配在栈上的变量提升到堆上,所以返回局部变量地址不会导致悬垂指针。这点和 C/C++ 完全不同,不必手动 malloc 或担心生命周期。
但要注意:
- 返回局部变量地址是安全的,但返回局部数组/切片的元素地址可能有问题(因为底层数组可能被回收)
- 若函数内创建大对象并返回其指针,可能增加 GC 压力,需结合性能权衡
- 不要依赖“栈分配”做性能优化——Go 不保证也不暴露栈/堆细节
func getCounter() *int {
x := 0 // x 会被自动移到堆上
return &x
}
p := getCounter()
*p = 100 // 完全合法
真正容易出错的地方不在语法,而在忘记解引用、忽略 nil 检查、或误以为返回局部变量地址是危险操作——Go 把这些底层细节藏好了,你只需盯紧 * 和 & 的配对,以及每个指针值是否真实指向有效内存。










