16 KiB
16 KiB
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) // 输出: 原始消息
}
- 最小作用域原则:将变量定义在最小必要作用域内
- 避免全局状态:尽可能使用局部变量替代全局变量
- 明确的变量命名:变量名应清晰表达用途,特别注意避免遮蔽
- 包级变量初始化:使用
var ()块集中声明相关全局变量 - 常量使用:使用 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
}
闭包原理:
- 闭包不是即时执行的,而是捕获创建时的变量环境
- 闭包中的变量会持续存在,直到没有引用它们的函数
- 多个闭包可以共享相同的变量环境
- 在循环中直接使用索引变量会导致所有闭包捕获相同的最终值(常见陷阱)
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("模拟操作失败")
}
- 执行时机:在包含它的函数执行
return语句或发生 panic 后、函数真正返回前执行 - 参数求值:参数在
defer语句执行时求值,函数在 deferred 时执行 - 执行顺序:LIFO(后进先出),与声明顺序相反
- 与 panic 的关系:无论是正常返回还是由于 panic 返回,defer 都会执行
- 作用域:在函数体的任何位置声明的 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)
}
}
// 陷阱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 方法值与方法表达式
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
}
- 必须有明确的终止条件 - 无限递归会导致栈溢出
- 递归规模应逐步缩小 - 典型模式:处理部分数据后递归剩余数据
- 避免重复计算 - 使用记忆化技术(memoization)存储已计算结果
- 考虑替代方案 - 对于可能深度递归的场景,优先考虑迭代实现
- 注意栈大小限制 - 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]
}