跳转至

2.5 函数定义、参数传递、返回值处理

学习目标

  • 函数声明与定义
  • 参数传递机制
  • 多返回值设计模式
  • 可变参数函数
  • 匿名函数与闭包

函数声明与定义

函数是 Go 语言的基本构建块,通过 func 关键字声明。一个完整的函数包括函数名、参数列表、返回值列表和函数体。

基本语法

func functionName(parameter1 type1, parameter2 type2) returnType {
    // 函数体
    return value
}

示例代码

package main

import "fmt"

// 无参数无返回值的函数
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 main() {
    sayHello()                    // Hello, World!
    greet("Alice")               // Hello, Alice!
    result := add(3, 5)          // 8
    product := multiply(4, 6)    // 24

    fmt.Printf("Addition result: %d\n", result)
    fmt.Printf("Multiplication result: %d\n", product)
}

参数传递机制

Go 语言中所有参数都是值传递,即函数接收的是参数的副本。对于引用类型(切片、映射、通道、指针等),虽然传递的是值的副本,但这个副本指向相同的内存地址。

值类型参数示例

package main

import "fmt"

// 修改值类型参数(不影响原值)
func modifyValue(x int) {
    x = x * 2
    fmt.Println("Inside modifyValue:", x) // 20
}

func main() {
    num := 10
    modifyValue(num)
    fmt.Println("After modifyValue:", num) // 10 (未改变)
}

引用类型参数示例

package main

import "fmt"

// 修改切片元素(会影响原切片)
func modifySlice(s []int) {
    s[0] = 100
    fmt.Println("Inside modifySlice:", s) // [100 2 3]
}

// 修改映射(会影响原映射)
func modifyMap(m map[string]int) {
    m["key"] = 200
    fmt.Println("Inside modifyMap:", m) // map[key:200]
}

func main() {
    // 切片示例
    slice := []int{1, 2, 3}
    modifySlice(slice)
    fmt.Println("After modifySlice:", slice) // [100 2 3]

    // 映射示例
    mapping := map[string]int{"key": 100}
    modifyMap(mapping)
    fmt.Println("After modifyMap:", mapping) // map[key:200]
}

指针参数示例

package main

import "fmt"

// 使用指针修改原值
func modifyWithPointer(x *int) {
    *x = *x * 2
    fmt.Println("Inside modifyWithPointer:", *x) // 20
}

func main() {
    num := 10
    modifyWithPointer(&num)
    fmt.Println("After modifyWithPointer:", num) // 20 (已改变)
}

多返回值设计模式

Go 语言支持函数返回多个值,这是处理错误和返回额外信息的常用模式。

基本多返回值

package main

import (
    "errors"
    "fmt"
)

// 返回两个值:结果和错误信息
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

// 命名返回值
func calculate(a, b int) (sum int, product int, difference int) {
    sum = a + b
    product = a * b
    difference = a - b
    return // 裸返回,自动返回命名返回值
}

func main() {
    // 错误处理示例
    result, err := divide(10, 2)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Printf("Division result: %.2f\n", result) // 5.00
    }

    // 多返回值接收
    s, p, d := calculate(5, 3)
    fmt.Printf("Sum: %d, Product: %d, Difference: %d\n", s, p, d) // 8, 15, 2

    // 忽略某些返回值
    sumOnly, _, _ := calculate(10, 5)
    fmt.Println("Sum only:", sumOnly) // 15
}

可变参数函数

可变参数函数可以接受数量可变的参数,使用 ... 语法。

基本可变参数

package main

import "fmt"

// 接收任意数量的int参数
func sum(numbers ...int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}

// 混合固定参数和可变参数
func greetAndSum(prefix string, numbers ...int) {
    total := sum(numbers...)
    fmt.Printf("%s: The sum is %d\n", prefix, total)
}

func main() {
    // 可变参数调用
    fmt.Println("Sum:", sum(1, 2, 3))          // 6
    fmt.Println("Sum:", sum(1, 2, 3, 4, 5))    // 15

    // 传递切片到可变参数
    numbers := []int{10, 20, 30}
    fmt.Println("Sum from slice:", sum(numbers...)) // 60

    // 混合参数调用
    greetAndSum("Result", 1, 2, 3, 4) // Result: The sum is 10
}

匿名函数与闭包

匿名函数是没有名称的函数,可以直接定义并使用。闭包是能够捕获外部变量的匿名函数。

匿名函数示例

package main

import "fmt"

func main() {
    // 立即执行的匿名函数
    func() {
        fmt.Println("I'm an anonymous function!")
    }()

    // 将匿名函数赋值给变量
    add := func(a, b int) int {
        return a + b
    }

    result := add(3, 4)
    fmt.Println("3 + 4 =", result) // 7

    // 作为参数传递
    operate := func(a, b int, operation func(int, int) int) int {
        return operation(a, b)
    }

    multiply := func(x, y int) int {
        return x * y
    }

    fmt.Println("5 * 6 =", operate(5, 6, multiply)) // 30
}

闭包示例

package main

import "fmt"

// 返回一个闭包
func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

// 闭包实现生成器
func sequenceGenerator(start, step int) func() int {
    current := start
    return func() int {
        value := current
        current += step
        return value
    }
}

func main() {
    // 计数器闭包
    myCounter := counter()
    fmt.Println(myCounter()) // 1
    fmt.Println(myCounter()) // 2
    fmt.Println(myCounter()) // 3

    // 另一个独立的计数器
    anotherCounter := counter()
    fmt.Println(anotherCounter()) // 1
    fmt.Println(anotherCounter()) // 2

    // 生成器闭包
    evenGenerator := sequenceGenerator(0, 2)
    fmt.Println(evenGenerator()) // 0
    fmt.Println(evenGenerator()) // 2
    fmt.Println(evenGenerator()) // 4

    oddGenerator := sequenceGenerator(1, 2)
    fmt.Println(oddGenerator()) // 1
    fmt.Println(oddGenerator()) // 3
    fmt.Println(oddGenerator()) // 5
}

实际应用:中间件模式

package main

import "fmt"

// 中间件函数类型
type Middleware func(string) string

// 基础函数
func baseFunction(name string) string {
    return "Hello, " + name
}

// 装饰器函数(接收函数,返回函数)
func decorate(middleware Middleware) Middleware {
    return func(name string) string {
        // 在执行前添加功能
        fmt.Println("Before execution")

        // 执行原函数
        result := middleware(name)

        // 在执行后添加功能
        fmt.Println("After execution")

        return result
    }
}

func main() {
    // 创建装饰后的函数
    decoratedHello := decorate(baseFunction)

    // 调用装饰后的函数
    result := decoratedHello("Gopher")
    fmt.Println("Result:", result)

    /* 输出:
    Before execution
    After execution
    Result: Hello, Gopher
    */
}

总结

通过本小节的学习,你应该掌握了:

  1. 函数声明与定义:使用 func 关键字定义函数,理解参数和返回值的语法
  2. 参数传递机制:Go 语言的值传递特性,以及如何通过指针修改原值
  3. 多返回值设计模式:利用多返回值处理错误和返回额外信息
  4. 可变参数函数:使用 ... 语法定义和调用可变参数函数
  5. 匿名函数与闭包:创建和使用匿名函数,理解闭包的概念和应用

这些概念是 Go 语言编程的基础,熟练掌握它们将帮助你编写更加灵活和强大的代码。在实际开发中,多思考如何合理使用这些特性来构建清晰、可维护的程序结构。


上一节2.4 指针与内存管理
下一节2.6 结构体与方法