Day 3|指针与内存模型(Go 中“共享与修改”的唯一手段)[Go 语言 30 天系统学习计划]

学习目标
从根本上理解 指针解决的是什么问题
明确 Go 中“值传递”与“共享修改”的边界
掌握 `&`、`*`、`new`、`nil` 的真实含义
知道在工程中 什么时候必须用指针,什么时候不该用

一、为什么 Go 需要指针

在 Day 2 我们已经确认一个事实:

Go 中 赋值、传参、返回,默认都是值拷贝

那么问题来了:

如果我就是想修改外部变量,怎么办?

答案只有一个: 指针(pointer)

二、什么是指针(最准确的定义)

指针是一个变量,保存的是“另一个变量的内存地址”

a := 10
p := &a

此时:

  • a:值是 10
  • &a:a 在内存中的地址
  • p:类型是 *int,值是 a 的地址
  • *p:通过地址访问 a 本身

三、指针的两种基本操作

1. 取地址(&)

a := 10
p := &a

含义:

把变量 a 的内存地址取出来


2. 解引用(*)

fmt.Println(*p) // 10

*p = 20
fmt.Println(a)  // 20

过程说明:

  1. p 保存的是地址
  2. *p 表示“访问该地址指向的值”
  3. 修改 *p,就是修改原变量

结论:

指针 = 间接访问 + 共享修改


四、指针与函数参数(最核心用法)

1. 不使用指针(无法修改)

func change(x int) {
    x = 100
}

func main() {
    a := 10
    change(a)
    fmt.Println(a) // 10
}

原因:

  • x 是 a 的拷贝

2. 使用指针(可以修改)

func change(x *int) {
    *x = 100
}

func main() {
    a := 10
    change(&a)
    fmt.Println(a) // 100
}

过程说明:

  1. &a 把 a 的地址传入
  2. x 保存地址
  3. *x = 100 修改原值

结论:

Go 没有“引用传参”,只有“传地址的值”


五、指针与 struct(工程中最常见)

1. struct 作为参数(值拷贝)

type User struct {
    Name string
}

func change(u User) {
    u.Name = "Jack"
}

func main() {
    u := User{Name: "Tom"}
    change(u)
    fmt.Println(u.Name) // Tom
}

2. struct 指针作为参数(推荐)

func change(u *User) {
    u.Name = "Jack"
}

func main() {
    u := User{Name: "Tom"}
    change(&u)
    fmt.Println(u.Name) // Jack
}

结论(非常重要):

  • struct 参与修改 → 必须用指针
  • struct 较大 → 必须用指针
  • 方法接收者 → 90% 用指针

六、方法接收者:值 vs 指针(必会)

type User struct {
    Name string
}

func (u User) SetName1(name string) {
    u.Name = name
}

func (u *User) SetName2(name string) {
    u.Name = name
}
func main() {
    u := User{Name: "Tom"}

    u.SetName1("Jack")
    fmt.Println(u.Name) // Tom

    u.SetName2("Jack")
    fmt.Println(u.Name) // Jack
}

结论:

  • 要修改对象 → 指针接收者
  • Go 会自动帮你解引用(语法糖)

七、new 与 & 的区别

1. 使用 new

p := new(int)
*p = 10

含义:

  • 分配一块内存
  • 初始化为零值
  • 返回指针

2. 使用 &(更常见)

x := 10
p := &x

对比总结

写法是否分配内存是否初始化常用场景
new(T)是(零值)很少直接用
&x已存在已有值最常用

八、nil 指针(高频 panic 来源)

1. 错误示例

var p *int
*p = 10 // panic

原因:

  • p 没有指向任何内存

2. 正确写法

p := new(int)
*p = 10

或:

x := 10
p := &x

结论:

指针必须“指向有效内存”才能解引用


九、Go 的指针是“安全的”

Go 刻意限制了指针能力:

  • ❌ 没有指针运算(p++ 不存在)
  • ❌ 不能随意偏移内存
  • ❌ 不能手动释放内存
  • ✅ 有 GC(垃圾回收)

结论:

Go 的指针是“工程安全指针”,不是 C 指针


十、什么时候该用指针?(工程判断标准)

必须用指针的场景

  • 需要修改变量
  • struct 较大(避免拷贝)
  • 方法接收者
  • 表示“可选值”(nil / 非 nil)

不该用指针的场景

  • int / bool / time.Duration
  • map / slice / chan(本身是引用语义)
  • 只读小对象

十一、自检问题(进入 Day 4 前必须能回答)

  1. Go 为什么坚持值传递?
  2. 指针到底解决了什么问题?
  3. new 和 & 的区别是什么?
  4. map / slice 为什么通常不用指针?
  5. 方法接收者如何选择?

预告:Day 4

Day 4|struct 设计、组合与嵌入

你将学会:

  • 如何用 struct 设计业务模型
  • 嵌入(embedding)的真正用途
  • 如何用组合替代继承
  • 工程中 struct 的正确“边界感”

发表评论