Files
notes/resource/go/Go 语言的函数.md
2026-03-01 01:43:46 +08:00

704 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)
}
}
// 陷阱2nil 接收者会导致 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]
}
```