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

16 KiB
Raw Blame History

1️⃣ 函数基础

1.1 函数定义与语法

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 等"引用类型",也是通过值传递的,只是它们的值本身包含了指针信息。

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 作用域规则

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),具有以下特性:

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 闭包的核心机制是函数值与相关变量的绑定。

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 机制深度解析

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 常见陷阱

// 陷阱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 方法值与方法表达式

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 递归函数

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 调整

记忆化递归优化示例:

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]
}