导航菜单

函数基础

函数(Function)

函数是 Go 语言中的核心构建单元。在 Go 中,函数是一等公民(First-class Citizen),可以作为参数传递、作为返回值返回、赋值给变量。Go 语言的程序执行从 main() 函数开始。

函数定义与调用

基本语法

func 函数名(参数列表) 返回值类型 {
    函数体
}
// 无参数无返回值
func sayHello() {
    fmt.Println("Hello, World!")
}

// 有参数无返回值
func greet(name string) {
    fmt.Printf("Hello, %s!\n", name)
}

// 有参数有返回值
func add(a int, b int) int {
    return a + b
}

// 参数类型相同时可以简写
func multiply(a, b int) int {
    return a * b
}

// 多个返回值
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("除数不能为零")
    }
    return a / b, nil
}

// 调用函数
func main() {
    sayHello()
    greet("Alice")
    result := add(3, 5)
    fmt.Println(result)  // 8

    quotient, err := divide(10, 3)
    if err != nil {
        fmt.Println("错误:", err)
    } else {
        fmt.Printf("结果: %.2f\n", quotient)  // 结果: 3.33
    }
}

多返回值

Go 支持函数返回多个值,这是 Go 处理错误的经典方式:

// 返回两个值
func divmod(a, b int) (int, int) {
    return a / b, a % b
}

// 使用
quotient, remainder := divmod(17, 5)
fmt.Println(quotient, remainder)  // 3 2

// 如果不关心某个返回值,使用 _ 忽略
quotient, _ = divmod(17, 5)
func readFile(path string) (string, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return "", fmt.Errorf("读取文件失败: %w", err)
    }
    return string(data), nil
}

// 调用
content, err := readFile("config.txt")
if err != nil {
    log.Fatal(err)
}
fmt.Println(content)

命名返回值

Go 支持为返回值命名,使代码更清晰,并支持”裸返回”(naked return):

// 命名返回值
func rectangle(width, height float64) (area float64, perimeter float64) {
    area = width * height
    perimeter = 2 * (width + height)
    return  // 裸返回:返回已命名的 area 和 perimeter
}

// 调用
a, p := rectangle(10, 5)
fmt.Printf("面积: %.2f, 周长: %.2f\n", a, p)
// 面积: 50.00, 周长: 30.00

命名返回值在 defer 中修改返回值的场景也非常有用:

func double(n int) (result int) {
    defer func() {
        result *= 2  // 在函数返回前修改返回值
    }()
    result = n
    return  // 实际返回 result * 2
}

func main() {
    fmt.Println(double(5))  // 10
}

可变参数

使用 ...T 语法声明可变参数函数:

// 可变参数函数
func sum(numbers ...int) int {
    total := 0
    for _, n := range numbers {
        total += n
    }
    return total
}

func main() {
    fmt.Println(sum(1, 2, 3))        // 6
    fmt.Println(sum(10, 20, 30, 40)) // 100
    fmt.Println(sum())               // 0
}

传递切片给可变参数

nums := []int{1, 2, 3, 4, 5}

// 使用 ... 将切片展开为可变参数
fmt.Println(sum(nums...))  // 15

// 如果直接传切片(不带 ...),编译错误
// sum(nums)  // 编译错误:cannot use nums (type []int) as type int

可变参数与其他参数结合

// 可变参数必须在参数列表的最后
func format(prefix string, values ...int) string {
    result := prefix + ": "
    for i, v := range values {
        if i > 0 {
            result += ", "
        }
        result += fmt.Sprintf("%d", v)
    }
    return result
}

func main() {
    fmt.Println(format("数字", 1, 2, 3))  // 数字: 1, 2, 3
}

匿名函数与闭包

匿名函数

Go 支持没有函数名的匿名函数。匿名函数可以直接调用或赋值给变量:

// 直接调用
func main() {
    result := func(a, b int) int {
        return a + b
    }(3, 5)  // 立即调用
    fmt.Println(result)  // 8
}

// 赋值给变量
func main() {
    add := func(a, b int) int {
        return a + b
    }
    fmt.Println(add(3, 5))  // 8
}

闭包

闭包(Closure)

闭包是一个函数值,它引用了其外部函数作用域中的变量。闭包不仅能访问这些变量,还能”记住”并修改它们。闭包是 Go 中实现函数式编程和状态封装的重要机制。

// 闭包:返回一个"累加器"函数
func counter() func() int {
    count := 0  // 外部变量
    return func() int {
        count++  // 闭包引用并修改外部变量
        return count
    }
}

func main() {
    c1 := counter()
    fmt.Println(c1())  // 1
    fmt.Println(c1())  // 2
    fmt.Println(c1())  // 3

    c2 := counter()  // 新的闭包实例,有独立的 count
    fmt.Println(c2())  // 1
    fmt.Println(c1())  // 4(c1 的 count 不受 c2 影响)
}
func makeGreeter(greeting string) func(string) string {
    return func(name string) string {
        return greeting + ", " + name + "!"
    }
}

func main() {
    hello := makeGreeter("Hello")
    hi := makeGreeter("Hi")

    fmt.Println(hello("World"))  // Hello, World!
    fmt.Println(hi("Go"))        // Hi, Go!
}

闭包的经典应用

// 1. 延迟计算(惰性求值)
func lazyValue() func() int {
    fmt.Println("计算中...")
    value := computeExpensiveValue()
    return func() int {
        return value  // 值已经计算好了,调用时直接返回
    }
}

// 2. 斐波那契生成器
func fibonacci() func() int {
    a, b := 0, 1
    return func() int {
        a, b = b, a+b
        return a
    }
}

func main() {
    fib := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(fib())
    }
    // 输出: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55
}

函数作为参数与返回值

Go 中函数是一等公民,可以赋值给变量、作为参数传递、作为返回值返回。

函数类型

// 定义函数类型
type Operation func(int, int) int

// 函数类型作为参数
func compute(a, b int, op Operation) int {
    return op(a, b)
}

func main() {
    add := func(a, b int) int { return a + b }
    sub := func(a, b int) int { return a - b }
    mul := func(a, b int) int { return a * b }

    fmt.Println(compute(10, 3, add))  // 13
    fmt.Println(compute(10, 3, sub))  // 7
    fmt.Println(compute(10, 3, mul))  // 30
}

函数作为返回值

// 返回不同策略的函数
func getStrategy(name string) func(int, int) int {
    switch name {
    case "add":
        return func(a, b int) int { return a + b }
    case "sub":
        return func(a, b int) int { return a - b }
    case "mul":
        return func(a, b int) int { return a * b }
    default:
        return func(a, b int) int { return 0 }
    }
}

func main() {
    strategy := getStrategy("add")
    fmt.Println(strategy(3, 5))  // 8
}

实际应用:中间件模式

// 带日志的函数包装器
func withLogging(fn func()) {
    fmt.Println("[开始] 调用函数")
    fn()
    fmt.Println("[结束] 函数返回")
}

// 带计时的函数包装器
func withTiming(fn func()) {
    start := time.Now()
    fn()
    elapsed := time.Since(start)
    fmt.Printf("执行耗时: %v\n", elapsed)
}

func main() {
    work := func() {
        time.Sleep(100 * time.Millisecond)
        fmt.Println("工作中...")
    }

    withLogging(withTiming(work))
}

递归

递归(Recursion)

递归是指函数直接或间接地调用自身。递归函数必须包含一个**基准情形(Base Case)**来终止递归,否则会导致栈溢出。

经典示例:阶乘

func factorial(n int) int {
    if n <= 1 {  // 基准情形
        return 1
    }
    return n * factorial(n-1)  // 递归调用
}

func main() {
    fmt.Println(factorial(5))  // 120 (5 * 4 * 3 * 2 * 1)
    fmt.Println(factorial(0))  // 1
}

斐波那契数列

// 简单递归(效率低,指数时间复杂度)
func fib(n int) int {
    if n <= 1 {
        return n
    }
    return fib(n-1) + fib(n-2)
}

// 使用闭包记忆化(Memoization)提高效率
func fibonacci() func(int) int {
    cache := make(map[int]int)
    var f func(int) int
    f = func(n int) int {
        if n <= 1 {
            return n
        }
        if v, ok := cache[n]; ok {
            return v
        }
        result := f(n-1) + f(n-2)
        cache[n] = result
        return result
    }
    return f
}

func main() {
    fib := fibonacci()
    fmt.Println(fib(10))  // 55
    fmt.Println(fib(50))  // 12586269025(记忆化后速度快很多)
}

init 函数

init 函数

init() 是 Go 中的一个特殊函数,它在包被导入时自动执行。每个源文件可以定义多个 init() 函数,它们在程序启动时按特定顺序执行。

init 函数的特性

  • 每个源文件可以有多个 init() 函数
  • init() 函数不能被调用,也不能有参数和返回值
  • init()main() 之前执行
  • 同一个文件中,多个 init() 按声明顺序执行
  • 不同文件中,按文件名的字典序执行

程序启动顺序

被导入的包的 init() → 当前包的 init() → main()
package main

import (
    "fmt"
)

var globalVar int

func init() {
    fmt.Println("第一个 init 函数执行")
    globalVar = 100
}

func init() {
    fmt.Println("第二个 init 函数执行")
    fmt.Println("globalVar =", globalVar)
}

func main() {
    fmt.Println("main 函数执行")
}
// 输出:
// 第一个 init 函数执行
// 第二个 init 函数执行
// globalVar = 100
// main 函数执行

init 函数的典型用途

// 1. 注册驱动(如数据库驱动)
import _ "github.com/go-sql-driver/mysql"  // init 中注册 "mysql" 驱动

// 2. 初始化全局配置
var config *Config

func init() {
    data, err := os.ReadFile("config.json")
    if err != nil {
        log.Fatal(err)
    }
    config = parseConfig(data)
}

// 3. 初始化全局变量
var (
    DB     *sql.DB
    Logger *log.Logger
)

func init() {
    var err error
    DB, err = sql.Open("mysql", "user:pass@/dbname")
    if err != nil {
        log.Fatal(err)
    }
    Logger = log.New(os.Stdout, "[APP] ", log.LstdFlags)
}

练习题

练习 1:可变参数函数

编写一个函数 join(sep string, parts ...string) string,将可变参数用指定的分隔符连接成一个字符串。

参考答案

解题思路:使用 strings.Join 函数,它接受一个字符串切片和一个分隔符。可变参数 parts 在函数体内就是 []string 类型。

代码

package main

import (
    "fmt"
    "strings"
)

func join(sep string, parts ...string) string {
    return strings.Join(parts, sep)
}

func main() {
    fmt.Println(join(", ", "Alice", "Bob", "Charlie"))
    // Alice, Bob, Charlie

    fmt.Println(join(" | ", "Go", "is", "awesome"))
    // Go | is | awesome

    fmt.Println(join("-", "2024", "01", "01"))
    // 2024-01-01

    fmt.Println(join(",", ""))  // 空字符串
}

练习 2:闭包实现斐波那契生成器

使用闭包实现一个函数 newFibGenerator(),返回一个函数,每次调用时返回斐波那契数列的下一个值。

参考答案

解题思路:闭包内维护两个变量 ab,每次调用时更新它们的值。

代码

package main

import "fmt"

func newFibGenerator() func() int {
    a, b := 0, 1
    return func() int {
        a, b = b, a+b
        return a
    }
}

func main() {
    fib := newFibGenerator()
    for i := 0; i < 15; i++ {
        fmt.Printf("%d ", fib())
    }
    fmt.Println()
    // 输出: 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610

    // 独立的生成器实例
    fib2 := newFibGenerator()
    fmt.Println(fib2())  // 1
    fmt.Println(fib2())  // 1
    fmt.Println(fib2())  // 2
}

关键点:每次调用 newFibGenerator() 都会创建一个新的闭包实例,拥有独立的 ab 变量。

练习 3:高阶函数实现 map 和 filter

使用函数类型实现类似函数式编程中的 mapfilter 操作:

  1. mapInts(nums []int, fn func(int) int) []int:对切片中每个元素应用函数
  2. filterInts(nums []int, fn func(int) bool) []int:保留满足条件的元素
参考答案

解题思路:定义函数类型参数,在函数体内遍历切片并应用回调函数。

代码

package main

import "fmt"

// mapInts 对切片中每个元素应用转换函数
func mapInts(nums []int, fn func(int) int) []int {
    result := make([]int, len(nums))
    for i, v := range nums {
        result[i] = fn(v)
    }
    return result
}

// filterInts 保留满足条件的元素
func filterInts(nums []int, fn func(int) bool) []int {
    var result []int
    for _, v := range nums {
        if fn(v) {
            result = append(result, v)
        }
    }
    return result
}

func main() {
    nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    // 每个元素平方
    squared := mapInts(nums, func(n int) int {
        return n * n
    })
    fmt.Println(squared)  // [1 4 9 16 25 36 49 64 81 100]

    // 筛选偶数
    evens := filterInts(nums, func(n int) bool {
        return n%2 == 0
    })
    fmt.Println(evens)  // [2 4 6 8 10]

    // 筛选大于 5 的元素
    greaterThanFive := filterInts(nums, func(n int) bool {
        return n > 5
    })
    fmt.Println(greaterThanFive)  // [6 7 8 9 10]

    // 组合使用:先筛选偶数,再平方
    evensSquared := mapInts(
        filterInts(nums, func(n int) bool { return n%2 == 0 }),
        func(n int) int { return n * n },
    )
    fmt.Println(evensSquared)  // [4 16 36 64 100]
}

搜索