函数:参数、返回值与多返回值
本章深入Go函数:命名返回值、多返回值+错误处理、匿名函数+闭包、defer机制。重点:Go函数是一等公民,支持多返回值和闭包;defer是独特的时间点执行模式。
前置知识:第3章 语法基础 学习目标:掌握Go函数特性、理解闭包陷阱、熟练使用defer
5.1 函数签名与命名返回值
Section titled “5.1 函数签名与命名返回值”函数基本结构
Section titled “函数基本结构”// 完整形式func add(a int, b int) int { return a + b}
// 相同类型可以合并func add(a, b int) int { return a + b}
// 无参数func greet() { fmt.Println("Hello!")}
// 无返回值func printSum(a, b int) { fmt.Println(a + b)}Python对比
Section titled “Python对比”def add(a, b): return a + b
def greet(): print("Hello!")
def print_sum(a, b): print(a + b)Go允许为返回值命名,使其成为命名变量:
func divide(a, b int) (quotient int, remainder int) { quotient = a / b // 命名变量赋值 remainder = a % b return // 裸return返回这些值}
// 调用q, r := divide(10, 3)fmt.Println(q, r) // 3 1命名返回值的工作原理
Section titled “命名返回值的工作原理”func split(sum int) (x, y int) { // 此时x, y已经声明,类型为int,值为0 // 相当于: // var x, y int
x = sum * 4 / 9 // 赋值 y = sum - x // 赋值
return // 返回x和y}**裸return(bare return)**只能用于短函数(如上所示),用于返回命名返回值。
返回值类型组合
Section titled “返回值类型组合”// 多返回值func getUser() (string, int) { return "Alice", 30}
// 命名+多返回值func getUser() (name string, age int) { name = "Alice" age = 30 return // 自动返回name和age}5.2 多返回值与错误处理模式
Section titled “5.2 多返回值与错误处理模式”本节目的:理解Go错误处理哲学、掌握多返回值和error类型
Go的错误哲学
Section titled “Go的错误哲学”Go没有异常机制,错误通过返回值传递:
// Python: 用异常处理错误def divide(a, b): if b == 0: raise ZeroDivisionError("除数不能为零") return a / b
try: result = divide(10, 0)except ZeroDivisionError as e: print(f"错误: {e}")// Go: 错误作为返回值func divide(a, b int) (int, error) { if b == 0 { return 0, errors.New("除数不能为零") } return a / b, nil}
result, err := divide(10, 0)if err != nil { fmt.Println("错误:", err)} else { fmt.Println("结果:", result)}error接口
Section titled “error接口”Go的标准错误接口:
type error interface { Error() string}所有错误都实现了这个接口。errors.New()创建简单错误:
import "errors"
err := errors.New("错误信息")fmt.Println(err.Error()) // 错误信息错误处理模式
Section titled “错误处理模式”模式一:立即检查
result, err := divide(10, 0)if err != nil { // 处理错误 return err // 或继续处理}模式二:传播错误
func processFile(filename string) error { data, err := os.ReadFile(filename) if err != nil { return err // 错误原样返回 } // 继续处理 return nil}模式三:包装错误(推荐)
import "fmt"
func processFile(filename string) error { data, err := os.ReadFile(filename) if err != nil { return fmt.Errorf("读取文件失败 %s: %w", filename, err) } return nil}fmt.Errorf的%w动词创建错误链,可用errors.Unwrap()解包。
如果确定不会出错,可以忽略error:
file, _ := os.Open("test.txt") // 忽略error// 风险:忽略错误可能导致后续问题函数可以返回多个错误:
func copyFile(src, dst string) (int64, error) { srcFile, err := os.Open(src) if err != nil { return 0, fmt.Errorf("打开源文件失败: %w", err) } defer srcFile.Close()
dstFile, err := os.Create(dst) if err != nil { return 0, fmt.Errorf("创建目标文件失败: %w", err) } defer dstFile.Close()
return io.Copy(dstFile, srcFile)}errors包常用函数
Section titled “errors包常用函数”import "errors"
// 创建errors.New("message")
// 判断errors.Is(err, io.EOF) // 错误链中是否有特定错误errors.As(err, new(*MyError)) // 错误链中是否有特定类型
// 错误链解包err2 := fmt.Errorf("包装: %w", err1)errors.Is(err2, err1) // true5.3 匿名函数与闭包
Section titled “5.3 匿名函数与闭包”本节目的:理解闭包概念、警惕循环变量捕获陷阱
// 声明并立即赋值给变量add := func(a, b int) int { return a + b}
result := add(1, 2) // 3// 立即执行result := func(a, b int) int { return a + b}(1, 2)
fmt.Println(result) // 3闭包(Closure)
Section titled “闭包(Closure)”闭包是引用了外部变量的匿名函数:
func adder() func(int) int { sum := 0 // 闭包捕获的变量 return func(x int) int { sum += x return sum }}
func main() { add := adder() // add是一个闭包 fmt.Println(add(1)) // 1 fmt.Println(add(2)) // 3 fmt.Println(add(3)) // 6
// 每次调用adder()创建新的闭包 add2 := adder() fmt.Println(add2(10)) // 10}常见错误:
func main() { // 错误:所有闭包共享同一个变量 funcs := []func(){} for i := 0; i < 3; i++ { funcs = append(funcs, func() { fmt.Println(i) // 打印的是循环结束后的i值 }) }
for _, f := range funcs { f() // 输出: 3 3 3(而非0 1 2) }}正确做法:在循环内创建新变量
func main() { funcs := []func(){} for i := 0; i < 3; i++ { i := i // 创建新变量 funcs = append(funcs, func() { fmt.Println(i) }) }
for _, f := range funcs { f() // 输出: 0 1 2 }}另一种方法:作为参数传递
func main() { funcs := []func(){} for i := 0; i < 3; i++ { v := i // 创建新变量 funcs = append(funcs, func() { fmt.Println(v) }) } // ...}Python对比
Section titled “Python对比”# Python闭包def adder(): sum = 0 def add(x): nonlocal sum sum += x return sum return add
add = adder()print(add(1)) # 1print(add(2)) # 3
# Python不需担心循环闭包问题funcs = []for i in range(3): funcs.append(lambda: print(i)) # 默认参数捕获当前值
for f in funcs: f() # 输出: 0 1 25.4 defer 延迟执行机制
Section titled “5.4 defer 延迟执行机制”本节目的:掌握defer的延迟执行特性、理解参数求值时机
defer基础
Section titled “defer基础”defer语句延迟函数执行,直到周围函数返回:
func readFile(filename string) error { file, err := os.Open(filename) if err != nil { return err }
// defer确保文件关闭 defer file.Close()
// 读取文件... return nil}defer执行顺序
Section titled “defer执行顺序”LIFO(后进先出):
func main() { defer fmt.Println("1") defer fmt.Println("2") defer fmt.Println("3")
fmt.Println("main")}
// 输出:// main// 3// 2// 1defer的常见用途
Section titled “defer的常见用途”1. 关闭资源
func process() { // 打开连接 conn, err := net.Dial("tcp", "localhost:8080") if err != nil { return } defer conn.Close()
// 使用连接...}2. 释放锁
var mu sync.Mutexvar counter int
func increment() { mu.Lock() defer mu.Unlock() counter++}3. 记录函数执行时间
func slowOperation() { defer trace("slowOperation")() // ... 操作}
func trace(name string) func() { start := time.Now() fmt.Printf("进入 %s\n", name) return func() { fmt.Printf("离开 %s (%v)\n", name, time.Since(start)) }}defer参数求值时机
Section titled “defer参数求值时机”defer语句中的参数在defer时求值,而非函数返回时:
func main() { i := 0 defer fmt.Println(i) // 打印0(此时i=0)
i = 10 return // 输出: 0}改变这个行为:使用命名返回值或闭包
func main() { i := 0 defer func() { fmt.Println(i) // 打印10(闭包捕获i的引用) }()
i = 10 return}defer与panic
Section titled “defer与panic”defer在panic时仍会执行:
func main() { defer fmt.Println("defer执行")
fmt.Println("panic前") panic("发生错误") fmt.Println("panic后") // 不会执行}
// 输出:// panic前// defer执行// panic: 发生错误recover捕获panic
Section titled “recover捕获panic”func safeCall() { defer func() { if r := recover(); r != nil { fmt.Println("捕获panic:", r) } }()
fmt.Println("可能panic的代码") panic("oops")}
func main() { safeCall() fmt.Println("继续执行")}
// 输出:// 可能panic的代码// 捕获panic: oops// 继续执行- 错误处理链:每个错误都检查,不要忽略
err != nil;用fmt.Errorf包装错误信息 - 避免defer in loop:循环内的defer会累积,函数返回时才执行,可能导致资源泄漏
- 闭包循环陷阱:循环内创建闭包时,循环变量是引用。用
func(id int)传参捕获当前值 - defer参数立即求值:
defer fmt.Println(x)会在defer时计算x,不是执行时 - defer vs return:defer在return之后、实际退出前执行;recover只能用于defer中
| Python | Go |
|---|---|
def f(a, b): return a+b | func f(a, b int) int { return a + b } |
| 多返回值需返回dict/tuple | 多返回值原生支持 |
| try/except处理错误 | if err != nil 处理错误 |
| lambda | func(a, b int) int { return a + b } |
| nonlocal捕获变量 | 闭包捕获外部变量 |
| 无 | defer延迟执行 |
| finally | defer(更强大) |
- 写一个divide函数,返回商和余数,使用命名返回值
- 写一个函数返回(int, error),练习错误处理
- 实现一个闭包:counter,每次调用递增1
- 写一个函数,用defer记录进入和退出时间
- 实现safeDivide,用recover捕获panic