跳到主要内容

Go 函数

本章将介绍 Go 语言中的函数,包括函数定义、参数、返回值和作用域。

函数基础

定义函数

func 函数名(参数) 返回值 {
// 函数体
return 返回值
}

基本示例

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

// 有参数无返回值
func greet(name string) {
fmt.Println("Hello,", name)
}

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

// 调用
sayHello()
greet("张三")
result := add(1, 2)
fmt.Println(result) // 3

参数

多参数

// 相同类型可以合并
func add(a, b int) int {
return a + b
}

// 不同类型分开写
func greet(name string, age int) {
fmt.Printf("Hello, %s, age: %d\n", name, age)
}

不定参数

不定参数允许传入零个或多个参数:

// 语法:...类型
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}

// 调用
fmt.Println(sum()) // 0
fmt.Println(sum(1)) // 1
fmt.Println(sum(1, 2)) // 3
fmt.Println(sum(1, 2, 3)) // 6

切片展开

可以使用切片调用不定参数函数:

nums := []int{1, 2, 3, 4, 5}
result := sum(nums...) // 展开切片
fmt.Println(result) // 15

参数是值拷贝

Go 中函数参数是值拷贝,不会修改原变量:

func modify(x int) {
x = 100
}

func main() {
a := 10
modify(a)
fmt.Println(a) // 10,原值不变
}

使用指针修改参数

func modify(x *int) {
*x = 100
}

func main() {
a := 10
modify(&a)
fmt.Println(a) // 100,原值被修改
}

返回值

单返回值

func add(a, b int) int {
return a + b
}

多返回值

Go 函数可以返回多个值:

func divide(a, b int) (int, int) {
quotient := a / b
remainder := a % b
return quotient, remainder
}

// 调用
q, r := divide(10, 3)
fmt.Println(q, r) // 3 1

// 忽略某个返回值
q, _ := divide(10, 3)

命名返回值

可以为返回值命名,函数结束时自动返回:

func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return // 自动返回 x 和 y
}

// 调用
a, b := split(100)
fmt.Println(a, b) // 44 56
小技巧

命名返回值使代码更清晰,但过度使用会使代码难以理解。建议仅在多个返回值的场景使用。

返回错误

Go 的惯用方式是通过返回 error 来处理错误:

func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("除数不能为零")
}
return a / b, nil
}

// 调用
result, err := divide(10, 0)
if err != nil {
fmt.Println("错误:", err)
} else {
fmt.Println("结果:", result)
}

函数类型

函数作为参数

Go 中函数是一等公民,可以作为参数传递:

func apply(fn func(int, int) int, a, b int) int {
return fn(a, b)
}

func main() {
result := apply(func(a, b int) int {
return a + b
}, 10, 20)
fmt.Println(result) // 30
}

函数作为返回值

func multiplier(factor int) func(int) int {
return func(x int) int {
return x * factor
}
}

func main() {
double := multiplier(2)
triple := multiplier(3)

fmt.Println(double(5)) // 10
fmt.Println(triple(5)) // 15
}

函数作为值

type Operation func(int, int) int

func add(a, b int) int {
return a + b
}

func main() {
var op Operation = add
fmt.Println(op(10, 20)) // 30

op = func(a, b int) int {
return a * b
}
fmt.Println(op(10, 20)) // 200
}

闭包

闭包是指一个函数可以访问并操作其外部作用域的变量:

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

func main() {
c1 := counter()
c2 := counter()

fmt.Println(c1()) // 1
fmt.Println(c1()) // 2
fmt.Println(c1()) // 3

fmt.Println(c2()) // 1
fmt.Println(c2()) // 2
}

闭包修改变量

func makeSuffix(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}

func main() {
addJpg := makeSuffix(".jpg")
addTxt := makeSuffix(".txt")

fmt.Println(addJpg("photo")) // photo.jpg
fmt.Println(addJpg("photo.jpg")) // photo.jpg
fmt.Println(addTxt("doc")) // doc.txt
}

defer

defer 用于延迟执行函数调用,常用于资源清理:

基本用法

func read() {
file, _ := os.Open("test.txt")
defer file.Close() // 函数结束时自动关闭

// 读取文件...
}

多个 defer

多个 defer 按后进先出(LIFO)顺序执行:

func main() {
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")

fmt.Println("主函数")
}

// 输出:
// 主函数
// 3
// 2
// 1

defer 与返回值

func f1() int {
i := 0
defer func() {
i++
}()
return i // 返回 0
}

func f2() (i int) {
defer func() {
i++
}()
return i // 返回 1
}

defer 常见用途

// 1. 关闭文件
func readFile(path string) {
file, err := os.Open(path)
if err != nil {
return
}
defer file.Close()

// 读取文件...
}

// 2. 解锁互斥锁
var mu sync.Mutex
var counter int

func increment() {
mu.Lock()
defer mu.Unlock()
counter++
}

// 3. 打印函数执行时间
func timeTrack(start time.Time, name string) {
elapsed := time.Since(start)
fmt.Printf("%s 执行耗时: %s\n", name, elapsed)
}

func slowFunction() {
defer timeTrack(time.Now(), "slowFunction")
time.Sleep(2 * time.Second)
}

恐慌和恢复

panic

panic 会导致程序崩溃,通常用于不可恢复的错误:

func panicExample() {
panic("程序崩溃")
fmt.Println("这行不会执行")
}

recover

recover 用于捕获 panic,防止程序崩溃:

func safeCall() {
defer func() {
if r := recover(); r != nil {
fmt.Println("捕获到 panic:", r)
}
}()

panic("发生错误")
fmt.Println("这行不会执行")
}

最佳实践

func safeFunction(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()

fn()
return nil
}

递归

函数可以调用自身:

// 阶乘
func factorial(n int) int {
if n <= 1 {
return 1
}
return n * factorial(n-1)
}

// 斐波那契数列
func fibonacci(n int) int {
if n <= 1 {
return n
}
return fibonacci(n-1) + fibonacci(n-2)
}

// 尾递归优化(Go 不支持尾递归优化)
func factorialTail(n, acc int) int {
if n <= 1 {
return acc
}
return factorialTail(n-1, n*acc)
}

递归终止条件

// 错误示例:没有终止条件
func countDown(n int) {
fmt.Println(n)
countDown(n - 1) // 无限递归
}

// 正确示例
func countDown(n int) {
if n < 0 {
return // 终止条件
}
fmt.Println(n)
countDown(n - 1)
}

main 函数

main 函数是程序入口:

package main

import "fmt"

func main() {
fmt.Println("程序开始")
}
注意
  • main 函数必须在 main 包中
  • 每个程序只能有一个 main 函数
  • main 函数不接受任何参数,也不返回任何值

init 函数

init 函数在包被导入时执行:

package main

import "fmt"

var name string

func init() {
name = "张三"
fmt.Println("init 函数执行")
}

func main() {
fmt.Println("main 函数执行:", name)
}

// 输出:
// init 函数执行
// main 函数执行: 张三
提示

init 函数常用于包级别的初始化,如设置配置、初始化全局变量等。

小结

  1. 函数定义:使用 func 关键字
  2. 参数:值传递,使用指针修改原值
  3. 返回值:支持多返回值,常用 error 处理
  4. 函数类型:函数可以作为参数和返回值
  5. 闭包:函数可以访问外部变量
  6. defer:延迟执行,常用于资源清理
  7. panic/recover:错误处理机制
  8. 递归:函数调用自身

练习

  1. 编写一个函数,计算两个数的和、差、积
  2. 编写一个函数,接受不定参数并计算平均值
  3. 使用闭包实现一个计数器
  4. 编写一个函数,使用 defer 在函数返回前打印执行时间
  5. 使用递归实现一个函数来计算斐波那契数列第 n 项
  6. 编写一个安全的除法函数,返回错误信息