10-指针:理解 Go 的内存地址

0 阅读4分钟

🔖 Go 语言学习教程 · 第 10 篇

📌 本系列基于 Go 1.23 编写,代码兼容 Go 1.21+

全文约 3500 字 | 阅读约 10 分钟


欢迎来到第十篇 👋

指针是很多初学者的"噩梦",但 Go 的指针设计得非常简洁安全。今天我们来理解指针的本质、使用场景和注意事项。

学完这篇,你会对 Go 的内存模型有更深的理解。让我们开始吧

✅ 上一篇练习题答案

以下是上一篇练习题的参考答案,欢迎在评论区分享你的解法~

1. 动手实践:minMax 函数

func minMax(nums []int) (min, max int) {
    if len(nums) == 0 {
        return 0, 0
    }
    min, max = nums[0], nums[0]
    for _, n := range nums[1:] {
        if n < min {
            min = n
        }
        if n > max {
            max = n
        }
    }
    return
}

2. 可变参数:join 函数

func join(sep string, parts ...string) string {
    if len(parts) == 0 {
        return ""
    }
    result := parts[0]
    for _, p := range parts[1:] {
        result += sep + p
    }
    return result
}
// join(", ", "a", "b", "c") → "a, b, c"

3. 思考题:defer 为什么是 LIFO 顺序?

LIFO(后进先出)保证资源释放的正确性。例如:

f1, _ := os.Open("file1")
defer f1.Close()  // 后执行
f2, _ := os.Open("file2")
defer f2.Close()  // 先执行

如果改成 FIFO,f1 会在 f2 之前关闭,但 f2 可能还在使用 f1 的资源,导致错误。

4. 拓展阅读

Go Blog Defer, Panic, and Recover 详细介绍了错误处理机制。 🚀


🧐 一、什么是指针?

指针就是一个内存地址。

x := 42
p := &x       // p 是 x 的地址(指针)

fmt.Println(x)   // 42(值)
fmt.Println(p)   // 0xc0000b4008(地址)
fmt.Println(*p)  // 42(通过指针取值)
  • & 取地址运算符:获取变量的内存地址
  • * 解引用运算符:通过地址获取值

💡 指针让我们能够间接访问和修改变量的值。


📖 二、指针的基本用法

通过指针修改值

func increment(n *int) {
	*n++  // 修改指针指向的值
}

x := 10
increment(&x)
fmt.Println(x)  // 11 ← 被修改了!

指针与结构体

指针最常见的用途是避免大结构体的拷贝:

type User struct {
	Name string
	Age  int
}

// 传指针:不拷贝,直接修改原对象
func (u *User) SetName(name string) {
	u.Name = name
}

// 传值:会拷贝一份副本
func (u User) GetName() string {
	return u.Name
}

三、nil 指针

未初始化的指针值为 nil:

var p *int
fmt.Println(p)       // <nil>

// 解引用 nil 指针会 panic
fmt.Println(*p)      // panic: runtime error: invalid memory address

💡 永远在解引用前检查指针是否为 nil,这是 Go 的最佳实践。

func safeDeref(p *int) int {
	if p == nil {
		return 0  // 或返回零值
	}
	return *p
}

📖 四、new 函数

new 用于分配内存并返回指针:

p := new(int)      // 分配 int 的内存,初始值为 0
fmt.Println(*p)    // 0
*p = 42
fmt.Println(*p)    // 42

💡 实际开发中,new 用得很少,更多用 & 或字面量初始化。


五、指针的常见场景

场景一:函数参数避免拷贝

// 传值:大结构体会被拷贝
func process(u User) { ... }

// 传指针:只传递地址,效率高
func processPtr(u *User) { ... }

场景二:修改外部变量

func setName(u *User, name string) {
	u.Name = name
}

u := User{}
setName(&u, "Go语言")
fmt.Println(u.Name)  // "Go语言"

场景三:接收者方法

type Counter struct {
	count int
}

// 指针接收者:修改原对象
func (c *Counter) Increment() {
	c.count++
}

c := &Counter{}
c.Increment()
fmt.Println(c.count)  // 1

📝 六、本篇小结

要点总结
指针本质内存地址
& 取地址获取变量的地址
* 解引用通过地址获取值
nil 指针未初始化,解引用会 panic
指针用途避免拷贝、修改外部变量、方法接收者
Go 指针特点不能运算(无指针加减),比 C 指针安全

💬 一句话总结:

Go 指针简洁安全:& 取地址,* 解引用;常用于避免拷贝和修改外部变量;不能做指针运算。


✏️ 七、练习题

  1. 🖥️ 动手实践:写一个 swap(a, b *int) 函数,交换两个整数的值。
  2. 🔧 结构体指针:定义一个 Point 结构体,写一个 Translate(p *Point, dx, dy int) 函数修改坐标。
  3. 🤔 思考题:Go 的指针和 C 的指针有什么区别?为什么 Go 不允许指针运算?
  4. 📚 拓展阅读:查阅 Go FAQ 中的 Does Go have pointers?。

💡 练习题答案会在下一篇中公布,欢迎在评论区分享你的答案~


关注我,一起学编程

如果本文对你有帮助,欢迎 点赞、在看、转发、关注。