Go语言函数定义与调用

函数是Go语言中的“一等公民”,可以像变量一样被传递、赋值和调用。Go函数支持多返回值、命名返回值、可变参数、闭包等特性,使得代码更加简洁和模块化。本章将系统讲解函数的定义、调用以及各种高级用法。

设计哲学:函数是Go程序的基本构造块,通过函数组合实现复杂逻辑。Go没有类,但方法可以绑定到类型上。

1. 函数定义与基本调用

使用func关键字定义函数。语法:func 函数名(参数列表) (返回值列表) { 函数体 }。

基础函数示例
package main

import "fmt"

// 无参数,无返回值
func sayHello() {
    fmt.Println("Hello, Go!")
}

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

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

func main() {
    sayHello()
    greet("World")
    sum := add(10, 20)
    fmt.Println("Sum:", sum)
}

如果连续参数类型相同,可以省略前面参数的类型名:func add(a, b int) int。

2. 参数传递机制

Go中所有参数都是值传递(拷贝值)。但切片、map、通道、接口等引用类型拷贝的是指针,因此函数内修改会影响外部。

// 值类型:整数
func modifyInt(x int) {
    x = 100
}
v := 10
modifyInt(v)
fmt.Println(v) // 10(原值不变)

// 引用类型:切片
func modifySlice(s []int) {
    s[0] = 999
}
nums := []int{1,2,3}
modifySlice(nums)
fmt.Println(nums[0]) // 999(被修改)

// 若想修改值类型本身,需传指针
func reallyModify(x *int) {
    *x = 100
}
reallyModify(&v)
fmt.Println(v) // 100
虽然切片和map是引用语义,但传递的是切片头或map指针的副本,仍为值传递。函数内对切片进行append操作若不返回新切片,外部看不到扩容后的结果。

3. 多返回值(Go特色)

函数可以返回多个值,常用于返回结果与错误。

// 返回商和余数
func divmod(a, b int) (int, int) {
    return a / b, a % b
}

// 实际使用:返回计算结果和错误信息
func safeDivide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

func main() {
    q, r := divmod(10, 3)
    fmt.Printf("10/3=商%d 余%d\n", q, r)

    res, err := safeDivide(10, 0)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println(res)
    }
}

4. 命名返回值

可以为返回值命名,这样函数内可以直接对它们赋值,使用return(裸返回)自动返回它们。命名返回值常用于文档化和缩短代码。

// 计算矩形面积和周长,返回值命名
func rectProps(length, width float64) (area, perimeter float64) {
    area = length * width
    perimeter = 2 * (length + width)
    return  // 裸返回,自动返回 area 和 perimeter
}

func main() {
    a, p := rectProps(3, 4)
    fmt.Printf("面积: %.2f, 周长: %.2f\n", a, p)
}
裸返回仅适用于短函数,长函数中使用显式return更清晰。

5. 可变参数函数

使用...语法接受零个或多个参数,参数在函数内部表现为切片类型。

func sum(nums ...int) int {
    total := 0
    for _, v := range nums {
        total += v
    }
    return total
}

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

    // 传入现有切片需用...解包
    numbers := []int{2,4,6}
    fmt.Println(sum(numbers...)) // 12
}

6. 函数是一等公民(作为值)

函数可以赋值给变量、作为参数传递、作为返回值。

// 赋值给变量
var operation func(int, int) int
operation = func(a, b int) int { return a * b }
fmt.Println(operation(3,4)) // 12

// 作为参数(回调)
func apply(arr []int, callback func(int) int) []int {
    result := make([]int, len(arr))
    for i, v := range arr {
        result[i] = callback(v)
    }
    return result
}

// 作为返回值
func makeMultiplier(factor int) func(int) int {
    return func(x int) int {
        return x * factor
    }
}

func main() {
    doubled := apply([]int{1,2,3}, func(x int) int { return x*2 })
    fmt.Println(doubled) // [2,4,6]

    multiplyBy5 := makeMultiplier(5)
    fmt.Println(multiplyBy5(7)) // 35
}

7. 匿名函数与闭包

匿名函数即没有名字的函数,可以直接定义并调用,或赋值给变量。闭包是指匿名函数捕获外部变量的能力。

// 立即执行匿名函数
func() {
    fmt.Println("匿名函数被调用")
}()

// 闭包:计数器
func newCounter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

c1 := newCounter()
fmt.Println(c1()) // 1
fmt.Println(c1()) // 2
c2 := newCounter()
fmt.Println(c2()) // 1(独立计数)
闭包捕获的外部变量是引用,因此可以跨调用保持状态。常用于封装局部状态。

8. 递归函数

Go支持函数递归调用,需注意终止条件。

func factorial(n int) int {
    if n == 0 {
        return 1
    }
    return n * factorial(n-1)
}

fmt.Println(factorial(5)) // 120

9. defer 在函数中的使用

defer确保函数返回前执行特定操作(如资源释放)。多个defer按LIFO顺序执行。

func readFile(filename string) error {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer f.Close()  // 保证文件最终被关闭
    // 处理文件...
    return nil
}

综合示例:函数组合与数据管道

使用高阶函数构建管道
package main

import "fmt"

type FilterFunc func(int) bool

func filter(nums []int, f FilterFunc) []int {
    result := make([]int, 0)
    for _, v := range nums {
        if f(v) {
            result = append(result, v)
        }
    }
    return result
}

func mapFunc(nums []int, f func(int) int) []int {
    res := make([]int, len(nums))
    for i, v := range nums {
        res[i] = f(v)
    }
    return res
}

func main() {
    data := []int{1, 2, 3, 4, 5, 6}
    // 过滤偶数
    evens := filter(data, func(v int) bool { return v%2 == 0 })
    fmt.Println("evens:", evens)

    // 将结果平方
    squares := mapFunc(evens, func(v int) int { return v * v })
    fmt.Println("squares:", squares)

    // 链式调用(手动组合)
    result := mapFunc(filter(data, func(v int) bool { return v > 3 }), func(v int) int { return v * 10 })
    fmt.Println("result:", result) // [40,50,60]
}
evens: [2 4 6]
squares: [4 16 36]
result: [40 50 60]
小结:函数是Go程序的核心。掌握多返回值、闭包、函数作为值等特性,可以写出更加灵活和模块化的代码。下一章将介绍结构体和方法,Go进行面向对象编程的方式。