🔖 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 指针简洁安全:
&取地址,*解引用;常用于避免拷贝和修改外部变量;不能做指针运算。
✏️ 七、练习题
- 🖥️ 动手实践:写一个
swap(a, b *int)函数,交换两个整数的值。 - 🔧 结构体指针:定义一个
Point结构体,写一个Translate(p *Point, dx, dy int)函数修改坐标。 - 🤔 思考题:Go 的指针和 C 的指针有什么区别?为什么 Go 不允许指针运算?
- 📚 拓展阅读:查阅 Go FAQ 中的 Does Go have pointers?。
💡 练习题答案会在下一篇中公布,欢迎在评论区分享你的答案~
关注我,一起学编程
如果本文对你有帮助,欢迎 点赞、在看、转发、关注。