704 lines
16 KiB
Markdown
704 lines
16 KiB
Markdown
# 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]
|
||
}
|
||
```
|