# 1️⃣ 函数基础 ## 1.1 函数定义与语法 ```go package main import ( "errors" "fmt" ) // 函数的基本定义格式 func main() { // 简单函数调用示例 fmt.Println("无返回值函数:") sayHello() fmt.Println("\n有参数有返回值函数:") fmt.Println("5 + 3 =", add(5, 3)) fmt.Println("\n多返回值函数:") result, err := divide(10, 2) if err != nil { fmt.Println("错误:", err) } else { fmt.Println("10 / 2 =", result) } fmt.Println("\n命名返回值函数:") area, perimeter := rectangle(5, 3) fmt.Printf("面积: %.2f, 周长: %.2f\n", area, perimeter) fmt.Println("\n可变参数函数:") fmt.Println("sum(1, 2, 3) =", sum(1, 2, 3)) numbers := []int{1, 2, 3, 4, 5} fmt.Println("sum(numbers...) =", sum(numbers...)) } // 无参数无返回值函数 func sayHello() { fmt.Println("Hello") } // 带参数和返回值的函数 // 多个相同类型的参数可以简写(x, y int) func add(x, y int) int { return x + y } // 多返回值函数(典型的 Go 错误处理模式) func divide(x, y float64) (float64, error) { if y == 0 { return 0, errors.New("除数不能为0") } return x / y, nil } // 命名返回值函数 // 可以直接使用空 return 返回命名变量 func rectangle(width, height float64) (area, perimeter float64) { area = width * height perimeter = 2 * (width + height) return // 直接返回命名变量 } // 可变参数函数 // ...int 表示接受任意数量的 int 参数 func sum(nums ...int) int { total := 0 // nums 在函数内部是一个 []int 切片 for _, num := range nums { total += num } return total } ``` ## 1.2 参数传递机制(关键澄清) Go 语言**只有值传递**这一种参数传递方式!这是一个常见的误解。即使是 slice、map、channel 等"引用类型",也是通过值传递的,只是它们的值本身包含了指针信息。 ```go package main import "fmt" func main() { // 值类型示例 fmt.Println("值类型传递示例:") a := 10 fmt.Println("调用前 a =", a) modifyInt(a) fmt.Println("调用后 a =", a) // 指针类型示例 fmt.Println("\n指针值传递示例:") b := 20 fmt.Println("调用前 b =", b) modifyIntPtr(&b) fmt.Println("调用后 b =", b) // 引用类型(内部有指针)示例 fmt.Println("\n引用类型值传递示例:") c := make([]int, 3) fmt.Printf("调用前 c = %#v, 地址 = %p\n", c, &c[0]) modifySlice(c) fmt.Printf("调用后 c = %#v, 地址 = %p\n", c, &c[0]) // 深度修改引用类型的示例 fmt.Println("\n切片重新分配内存示例:") d := []int{1, 2, 3} fmt.Println("调用前 d =", d) reallocSlice(d) fmt.Println("调用后 d =", d) } // ❌ 值传递:修改的是副本,不影响原始值 func modifyInt(a int) { a = 100 fmt.Println("modifyInt 内 a =", a) } // ✅ 通过指针值传递:修改指针指向的内容,影响原始值 func modifyIntPtr(a *int) { *a = 200 fmt.Println("modifyIntPtr 内 *a =", *a) } // ✅ 修改引用类型的内容(不影响切片头本身) func modifySlice(s []int) { s[0] = 300 fmt.Printf("modifySlice 内 s = %#v, 地址 = %p\n", s, &s[0]) } // ❌ 尝试重新分配内存不会影响调用方 func reallocSlice(s []int) { s = append(s, 4) s[0] = 400 fmt.Println("reallocSlice 内 s =", s) } ``` **关键总结**: - Go **只有值传递**,不存在引用传递 - 对于基本类型(int, string, struct等),传递的是数据副本 - 对于引用类型(slice, map, channel等),传递的是包含指针信息的结构体副本 - 要修改调用方的数据,需要使用指针传递 ## 1.3 作用域规则 ```go package main import "fmt" // 全局变量(包级作用域) var globalVar = "全局变量" // 标准化后的变量命名示例 var ( DebugMode = false MaxRetries = 3 ) func main() { fmt.Println("=== Go 作用域规则演示 ===") // 局部变量(函数级作用域) localVar := "局部变量" fmt.Println("main 函数内:", globalVar, localVar) // 块级作用域示例 if score := 90; score >= 60 { // score 只在 if 块内可见 fmt.Printf("成绩为 %d: 及格!\n", score) // 可以访问外层变量 fmt.Println("可以访问全局变量:", globalVar) } // 无法访问 score,会编译错误 // fmt.Println(score) // 变量遮蔽演示 name := "外层变量" { name := "内层变量" // 遮蔽了外层变量 fmt.Println("块内:", name) } fmt.Println("块外:", name) shadowingExample() } // 变量遮蔽的另一个示例 func shadowingExample() { message := "原始消息" if message := "块级消息"; true { fmt.Println("if 块内:", message) // 输出: 块级消息 } fmt.Println("函数内:", message) // 输出: 原始消息 } ``` 1. **最小作用域原则**:将变量定义在最小必要作用域内 2. **避免全局状态**:尽可能使用局部变量替代全局变量 3. **明确的变量命名**:变量名应清晰表达用途,特别注意避免遮蔽 4. **包级变量初始化**:使用 `var ()` 块集中声明相关全局变量 5. **常量使用**:使用 const 声明不会改变的值,提高代码可读性 --- # 2️⃣ 函数高级特性 ## 2.1 函数作为一等公民 Go 中函数是**一等公民**(first-class citizen),具有以下特性: ```go package main import "fmt" func main() { fmt.Println("\n=== 函数作为一等公民 ===") // 1. 函数赋值给变量 sumFunc := func(a, b int) int { return a + b } fmt.Println("函数变量 sum(5, 3):", sumFunc(5, 3)) // 2. 作为参数传递 fmt.Println("操作结果:", operate(5, 3, sumFunc)) multiply := func(a, b int) int { return a * b } fmt.Println("操作结果:", operate(5, 3, multiply)) // 3. 作为返回值 adder := createAdder(10) fmt.Println("adder(5):", adder(5)) // 输出: 15 // 4. 匿名函数直接调用 func(message string) { fmt.Println("匿名函数直接执行:", message) }("Hello World") } // 函数作为参数 func operate(a, b int, op func(int, int) int) int { return op(a, b) } // 函数作为返回值(闭包) func createAdder(x int) func(int) int { return func(y int) int { return x + y } } ``` ## 2.2 闭包详解 闭包(closure)是指能够访问并记住其创建环境的匿名函数。Go 闭包的**核心机制**是函数值与相关变量的绑定。 ```go package main import "fmt" func main() { fmt.Println("\n=== 闭包深入解析 ===") // 基本闭包示例 counter := createCounter() fmt.Println("counter():", counter()) // 1 fmt.Println("counter():", counter()) // 2 // 闭包共享变量示例 nextID, reset := createUserSession() fmt.Println("用户ID:", nextID()) // 1001 fmt.Println("用户ID:", nextID()) // 1002 reset() fmt.Println("重置后用户ID:", nextID()) // 1001 // 闭包与循环问题 fmt.Println("\n循环中创建闭包问题修复:") f := createFunctions() for i, fn := range f { fmt.Printf("函数 %d 执行结果: %d\n", i, fn()) } } // 基础计数器闭包 func createCounter() func() int { count := 0 return func() int { count++ return count } } // 复杂闭包示例:共享状态 func createUserSession() (nextID func() int, reset func()) { currentID := 1000 nextID = func() int { currentID++ return currentID } reset = func() { currentID = 1000 } return nextID, reset } // [关键] 循环中闭包问题的正确处理 func createFunctions() []func() int { functions := make([]func() int, 3) // 方案1:在循环内创建新变量 for i := range functions { i := i // 创建新的i变量绑定到每个闭包 functions[i] = func() int { return i * 10 } } // 方案2:在循环中创建函数 // for i := range functions { // functions[i] = func(idx int) func() int { // return func() int { return idx * 10 } // }(i) // } return functions } ``` **闭包原理**: 1. 闭包不是即时执行的,而是**捕获创建时的变量环境** 2. 闭包中的变量会持续存在,直到没有引用它们的函数 3. 多个闭包可以共享相同的变量环境 4. 在循环中直接使用索引变量会导致所有闭包捕获相同的最终值(常见陷阱) ## 2.3 defer 机制深度解析 ```go package main import ( "fmt" "os" ) func main() { fmt.Println("\n=== defer 机制详解 ===") // 1. 基本执行顺序 (LIFO) illustrateDeferOrder() // 2. 参数求值时机 illustrateDeferEvaluation() // 3. 实际应用场景:资源清理 handleFile() // 4. defer + 错误处理模式 if err := processResource(); err != nil { fmt.Println("处理过程中出现错误:", err) } } // 演示 defer 的执行顺序 (后进先出) func illustrateDeferOrder() { fmt.Println("\n--- defer 执行顺序 (LIFO) ---") defer fmt.Println("defer 1: 最后执行") defer fmt.Println("defer 2: 倒数第二执行") defer fmt.Println("defer 3: 倒数第三执行") fmt.Println("立即执行: 主函数主体") } // 演示 defer 参数求值时机 func illustrateDeferEvaluation() { fmt.Println("\n--- defer 参数求值时机 ---") i := 1 // defer 语句执行时,参数就已经求值 defer fmt.Println("直接值传递(执行时):", i) // 捕获当前 i 值 (1) // 闭包捕获:捕获的是变量引用 defer func() { fmt.Println("闭包捕获(返回时):", i) }() // i 在此时尚未改变 i = 2 // 参数传递方式:参数在 defer 语句执行时求值 defer func(j int) { fmt.Println("参数传递方式:", j) // j 是 2,因为 defer 执行时 i 是 2 }(i) i = 3 } // 实际应用场景:文件处理 func handleFile() { fmt.Println("\n--- 文件处理 with defer ---") filename := "example.txt" // 创建示例文件 file, err := os.Create(filename) if err != nil { fmt.Println("创建文件失败:", err) return } // 确保文件被关闭 defer func() { fmt.Println("关闭文件...") if err := file.Close(); err != nil { fmt.Println("关闭文件时出错:", err) } }() // 写入内容 if _, err := file.WriteString("Hello, defer!"); err != nil { fmt.Println("写入文件失败:", err) return } fmt.Println("文件写入成功") } // defer + 错误处理最佳实践 func processResource() error { fmt.Println("\n--- defer + 错误处理最佳实践 ---") resource, err := acquireResource() if err != nil { return fmt.Errorf("获取资源失败: %w", err) } // 确保资源被释放,即使后面发生错误 defer func() { fmt.Println("释放资源...") releaseResource(resource) }() // 模拟业务逻辑 fmt.Println("处理资源中...") if err := performOperation(resource); err != nil { return fmt.Errorf("操作失败: %w", err) } // 操作成功 fmt.Println("操作成功完成!") return nil } // 模拟资源操作 func acquireResource() (string, error) { fmt.Println("获取资源...") return "resource_data", nil } func releaseResource(r string) { fmt.Printf("资源 %q 已释放\n", r) } func performOperation(r string) error { // 模拟成功操作 return nil // return errors.New("模拟操作失败") } ``` 1. **执行时机**:在包含它的函数执行 `return` 语句或发生 panic 后、函数真正返回前执行 2. **参数求值**:参数在 `defer` 语句执行时求值,函数在 deferred 时执行 3. **执行顺序**:LIFO(后进先出),与声明顺序相反 4. **与 panic 的关系**:无论是正常返回还是由于 panic 返回,defer 都会执行 5. **作用域**:在函数体的任何位置声明的 defer 都会在函数返回前执行 **defer 常见陷阱**: ```go // 陷阱1:在循环中使用 defer 可能导致资源延迟释放 func badLoopDefer() { for i := 0; i < 10; i++ { file, _ := os.Open(fmt.Sprintf("file_%d.txt", i)) defer file.Close() // 所有关闭操作都延迟到最后 } // 其他代码执行时已打开10个文件,但未关闭 } // 正确做法:使用内部函数 func goodLoopDefer() { for i := 0; i < 10; i++ { func(i int) { file, _ := os.Open(fmt.Sprintf("file_%d.txt", i)) defer file.Close() // 处理文件 }(i) } } // 陷阱2:nil 接收者会导致 panic func badHTTPDefer() { resp, err := http.Get("https://example.com") if err != nil { // 此处 resp 为 nil defer resp.Body.Close() // 会导致 panic return } // ... } // 正确做法:先检查 nil 再 defer func goodHTTPDefer() { resp, err := http.Get("https://example.com") if err != nil { return err } defer resp.Body.Close() // 此时 resp 不为 nil // ... } ``` ## 2.4 方法值与方法表达式 ```go package main import "fmt" // 定义一个简单的类型 type Dog struct { name string } // 为 Dog 类型定义方法 func (d Dog) Bark(volume int) { vol := "" for i := 0; i < volume; i++ { vol += "!" } fmt.Printf("%s: 汪%s\n", d.name, vol) } // 方法表达式示例 func (d Dog) WithPrefix(prefix string) string { return fmt.Sprintf("[%s] %s", prefix, d.name) } func main() { fmt.Println("\n=== 方法值与方法表达式 ===") dog := Dog{name: "小黑"} // 1. 方法值:绑定接收者 bark := dog.Bark bark(1) // 小黑: 汪! bark(3) // 小黑: 汪!!! // 2. 方法表达式:不绑定接收者 generalBark := Dog.Bark generalBark(dog, 2) // 小黑: 汪!! // 方法表达式在函数参数中的应用 processDog(dog, Dog.WithPrefix) } // 使用方法表达式作为参数 func processDog(d Dog, formatter func(Dog, string) string) { fmt.Println("处理后的名字:", formatter(d, "宠物")) } ``` --- ## 3️⃣ 递归与特殊函数模式 ### 3.1 递归函数 ```go package main import ( "fmt" ) func main() { fmt.Println("\n=== 递归函数详解 ===") // 基础递归示例 fmt.Println("sumRecursive(5) =", sumRecursive(5)) // 递归深度问题 showRecursionLimit() // 递归优化示例 fmt.Println("\n斐波那契数列:") fmt.Println("fibBad(10) =", fibBad(10)) // 低效递归 fmt.Println("fibGood(10) =", fibGood(10)) // 优化版本 } // 递归求和函数 func sumRecursive(n int) int { // 1. 终止条件 if n <= 1 { return n } // 2. 递归调用 return n + sumRecursive(n-1) } // 递归深度限制示例 func showRecursionLimit() { fmt.Println("\n递归深度限制演示:") defer func() { if r := recover(); r != nil { fmt.Println("递归深度过大导致栈溢出:", r) } }() // 尝试过深的递归 var deepRecursion func(int) deepRecursion = func(n int) { if n%1000 == 0 { fmt.Printf("递归深度: %d\n", n) } deepRecursion(n + 1) } deepRecursion(0) } // 低效的斐波那契递归(存在大量重复计算) func fibBad(n int) int { if n <= 1 { return n } return fibBad(n-1) + fibBad(n-2) } // 优化的斐波那契(迭代方法) func fibGood(n int) int { if n <= 1 { return n } a, b := 0, 1 for i := 2; i <= n; i++ { a, b = b, a+b } return b } ``` 1. **必须有明确的终止条件** - 无限递归会导致栈溢出 2. **递归规模应逐步缩小** - 典型模式:处理部分数据后递归剩余数据 3. **避免重复计算** - 使用记忆化技术(memoization)存储已计算结果 4. **考虑替代方案** - 对于可能深度递归的场景,优先考虑迭代实现 5. **注意栈大小限制** - Go 默认栈大小约 1-8MB,可使用 `debug.SetMaxStack` 调整 记忆化递归优化示例: ```go package main import "fmt" func main() { fmt.Println("\n记忆化斐波那契递归:") cache := make(map[int]int) fmt.Println("fibMemo(10) =", fibMemo(10, cache)) } // 记忆化斐波那契递归 func fibMemo(n int, cache map[int]int) int { // 检查是否已计算 if val, found := cache[n]; found { return val } // 基础情况 if n <= 1 { cache[n] = n return n } // 递归计算并保存结果 cache[n] = fibMemo(n-1, cache) + fibMemo(n-2, cache) return cache[n] } ```