Files
cs-note/hzh/GolangStar/Go语言基础/Go语言函数.md
T

4.4 KiB

tags, create time
tags create time
go
golang
go基础语法
函数
2026-06-07 15:00

Go 语言函数

概述

Go 的函数是一等公民:可以赋值给变量、作为参数传递、返回匿名函数(闭包)。本文覆盖多返回值、变参、闭包、匿名函数和值传递机制。

正文

为什么 Go 函数支持多返回值?

在多数语言中,函数只能返回一个值——错误信息需要单独处理(如异常或输出参数)。Go 的选择是:让错误成为显式的返回值。

name, age, err := GetUser(42)
if err != nil {
    return err
}

这比隐藏的错误处理更透明——编译器强制你处理每一个返回值。

基本定义与调用

func swap(x, y string) (string, string) {
    return y, x
}

a, b := swap("Google", "Hello")
fmt.Println(a, b)  // Hello Google

无返回值时省略返回类型;有返回值时必须写:

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

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

命名返回值

Go 允许给返回值命名,return 可以不写具体值(直接返回):

func Split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return  // 空白 return,返回当前 x, y 的值
}

[!tip] 💡 命名返回值的用途 虽然不常用,但在复杂函数的"提前返回"场景中可以让代码更清晰——读者一眼就知道返回了什么。

变参

变参函数接受零个或多个同类型参数:

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

Sum(1, 2, 3)       // 6
Sum(1, 2, 3, 4, 5) // 15

// 将切片展开传入
nums := []int{1, 2, 3, 4}
Sum(nums...)  // 10

[!warning] ⚠️ ...int 底层是 []int 在函数内部,nums 就是一个 slice。用 range nums 遍历时要注意前面提到的闭包陷阱。

函数是一等公民

函数可以赋值给变量、作为参数传递:

// 函数变量
getSquareRoot := func(x float64) float64 {
    return math.Sqrt(x)
}
fmt.Println(getSquareRoot(9))  // 3

// 函数作为参数(回调)
type Calculator func(int, int) int

func apply(a, b int, op Calculator) int {
    return op(a, b)
}

result := apply(3, 4, func(x, y int) int {
    return x + y
})  // 7

匿名函数与闭包

匿名函数是没有名称的函数,通常内联使用:

func() {
    fmt.Println("我是匿名函数")
}()  // 末尾 () 立即执行

闭包 = 匿名函数 + 捕获的外部变量。闭包会"记住"它创建时的环境变量:

func counter() func() int {
    i := 0
    return func() int {
        i++
        return i
    }
}

next := counter()
fmt.Println(next())  // 1
fmt.Println(next())  // 2
fmt.Println(next())  // 3

next2 := counter()   // 全新的计数器
fmt.Println(next2()) // 1 — 独立于 next

[!note] 📝 每次调用 counter() 都创建新的闭包环境 next 和 next2 各自维护独立的 i 变量,互不影响。这是闭包的核心特性。

graph LR
    A["counter()"] --> B["i = 0"]
    B --> C["返回闭包 → next"]
    C --> D["next() 调用: i→1"]
    D --> E["next() 调用: i→2"]
    A --> F["counter() 再次调用"]
    F --> G["i = 0 新变量"]
    G --> H["返回闭包 → next2"]
    H --> I["next2() 调用: i→1"]

[!warning] ⚠️ 闭包捕获的是引用 如果外部变量在闭包外被修改,闭包看到的也是修改后的值。配合 for range 使用时要特别注意(见「循环」章节)。

值传递:Go 只有值传递

Go 的所有参数传递都是值传递——参数被拷贝一份传给函数。但对于 slice、map、chan 这些引用类型,拷贝的是内部的指针,所以修改内容会影响原数据。

func addOne(x int) {
    x++  // 只修改了副本
}

func main() {
    a := 5
    addOne(a)
    fmt.Println(a)  // 5 — 没变!
}

如果需要修改原值,传指针:

func addOnePtr(x *int) {
    *x++
}

a := 5
addOnePtr(&a)
fmt.Println(a)  // 6 ✅

关键要点总结

特性 说明
多返回值 Go 独有,广泛用于错误处理
变参 ...T 底层是 []T,可用 ... 展开 slice
闭包 匿名函数捕获外部变量,形成独立状态
值传递 所有参数都是值拷贝,引用类型的"引用"本质是指针拷贝