04-functions-pointers
本章节介绍Go语言的函数定义、指针使用以及相关高级特性。
学习目标
完成本章节学习后,你将能够:
- 定义和调用函数
- 使用多返回值和可变参数
- 理解匿名函数和闭包
- 掌握指针的概念和使用
- 理解值传递和引用传递
章节内容
01-函数
核心内容:
- 函数定义与调用
- 多返回值
- 可变参数
- 匿名函数
- 闭包与作用域
- init函数详解
- defer执行顺序陷阱
学习时间:2-3天
实践要求:
- [ ] 编写多种形式的函数
- [ ] 使用多返回值处理错误
- [ ] 实现可变参数函数
- [ ] 理解闭包的捕获机制
02-指针
核心内容:
- 指针基础概念
- new和make的区别
- 指针与函数参数传递
- 指针的安全性
- 指针与数组/切片
学习时间:1-2天
实践要求:
- [ ] 理解指针的基本操作
- [ ] 使用指针修改函数外部变量
- [ ] 理解new和make的区别
- [ ] 掌握指针的最佳实践
前置知识
- 完成02-basic-syntax章节
- 完成03-data-structures章节
- 理解变量和数据类型
核心概念
函数定义
go
// 基本函数
func add(a, b int) int {
return a + b
}
// 多返回值
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// 命名返回值
func calc(a, b int) (sum, product int) {
sum = a + b
product = a * b
return // 自动返回命名变量
}
// 可变参数
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
// 使用
sum(1, 2, 3, 4, 5) // 15匿名函数与闭包
go
// 匿名函数
add := func(a, b int) int {
return a + b
}
result := add(1, 2)
// 闭包:捕获外部变量
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
c := counter()
fmt.Println(c()) // 1
fmt.Println(c()) // 2
fmt.Println(c()) // 3指针基础
go
// 指针声明和使用
var x int = 10
var p *int = &x
fmt.Println(*p) // 10 (解引用)
*p = 20
fmt.Println(x) // 20
// 指针作为函数参数
func increment(p *int) {
*p++ // 修改指针指向的值
}
x := 10
increment(&x)
fmt.Println(x) // 11new vs make
go
// new: 分配内存,返回指针
p := new(int) // *int, 值为0
// make: 只用于slice、map、channel
slice := make([]int, 0, 10)
m := make(map[string]int)
ch := make(chan int)
// 区别
// new: 返回指针
// make: 返回初始化后的值(不是指针)学习重点
1. defer执行顺序
go
func example() {
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
fmt.Println("function body")
}
// 输出顺序:
// function body
// 3
// 2
// 1
// LIFO(后进先出)2. 闭包捕获变量
go
// ❌ 常见错误
funcs := []func(){}
for i := 0; i < 3; i++ {
funcs = append(funcs, func() {
fmt.Println(i) // 捕获的是变量i本身
})
}
for _, f := range funcs {
f() // 输出: 3, 3, 3
}
// ✅ 正确做法
for i := 0; i < 3; i++ {
i := i // 创建新变量
funcs = append(funcs, func() {
fmt.Println(i)
})
}
// 输出: 0, 1, 23. 值传递 vs 指针传递
go
// 值传递:拷贝副本
func modifyValue(x int) {
x = 20 // 不影响原变量
}
// 指针传递:传递地址
func modifyPointer(x *int) {
*x = 20 // 修改原变量
}
num := 10
modifyValue(num)
fmt.Println(num) // 10
modifyPointer(&num)
fmt.Println(num) // 20检查清单
完成本章节后,验证你的掌握程度:
- [ ] 能够定义各种形式的函数
- [ ] 熟练使用多返回值
- [ ] 理解可变参数的使用
- [ ] 掌握匿名函数和闭包
- [ ] 理解defer的执行顺序
- [ ] 理解指针的概念
- [ ] 掌握指针作为函数参数
- [ ] 理解new和make的区别
最佳实践
1. 何时使用指针
使用指针的场景:
- 需要修改函数外部变量
- 大结构体,避免拷贝开销
- 需要表示nil(可选参数)
- 实现修改接收者的方法
不使用指针的场景:
- 小数据类型(int、float等)
- 只读操作
- 不需要nil表示
2. 函数设计原则
go
// ✅ 好的函数设计
func ReadFile(path string) ([]byte, error) {
// 单一职责
// 清晰的错误处理
// 有意义的名称
}
// ❌ 避免
func doStuff(x interface{}) interface{} {
// 职责不清
// 类型不安全
// 名称不明确
}3. defer使用建议
go
// ✅ 推荐用法:资源清理
file, err := os.Open("file.txt")
if err != nil {
return err
}
defer file.Close() // 确保文件关闭
// ✅ 推荐用法:解锁
mu.Lock()
defer mu.Unlock()
// ⚠️ 在循环中谨慎使用
for i := 0; i < 1000; i++ {
defer cleanup() // 延迟到函数结束,可能内存泄露
}常见陷阱
1. defer在循环中
go
// ❌ 错误:所有defer在函数结束时执行
func processFiles(files []string) error {
for _, file := range files {
f, err := os.Open(file)
if err != nil {
return err
}
defer f.Close() // 文件不会立即关闭!
}
}
// ✅ 正确:使用匿名函数
func processFiles(files []string) error {
for _, file := range files {
err := func() error {
f, err := os.Open(file)
if err != nil {
return err
}
defer f.Close() // 在匿名函数结束时关闭
// 处理文件...
return nil
}()
if err != nil {
return err
}
}
}2. nil指针检查
go
var p *int
fmt.Println(*p) // panic: nil pointer dereference
// ✅ 检查nil
if p != nil {
fmt.Println(*p)
}练习项目
- 实现一个简单的计算器(多种运算)
- 编写一个计数器(使用闭包)
- 实现链表结构(使用指针)
- 编写一个简单的栈(使用切片和指针)