404 Not Found

404 Not Found


nginx

函数基础

函数基础

函数就像生活中的"自动贩卖机"——你投入参数,它按照既定规则处理,然后返回结果。在Go语言中,函数是一等公民,它可以赋值给变量、作为参数传递、作为返回值,甚至可以没有名字。掌握函数,是写出优雅Go代码的关键一步。


1. 核心概念

概念 说明
func 定义 使用 func 关键字声明函数,支持参数和返回值类型
多返回值 Go函数可以返回多个值,常用于返回结果+错误
命名返回值 返回值可以命名,在函数体内直接使用,return 时自动返回
可变参数 使用 ...Type 接收任意数量的参数
匿名函数 没有名字的函数,常用于即时调用或赋值给变量
闭包 匿名函数捕获外部变量,形成闭包
init 函数 每个包可有多个 init 函数,程序启动时自动执行
defer 延迟执行,函数返回前按栈顺序执行

2. 基本语法/用法

函数定义

GO
// 基本函数定义
func 函数名(参数列表) 返回值类型 {
    // 函数体
    return 值
}

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

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

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

多返回值

GO
// 返回两个值:结果和错误
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("除数不能为零")
    }
    return a / b, nil
}

// 调用时接收两个返回值
result, err := divide(10, 3)

命名返回值

GO
// 命名返回值:函数体内直接使用变量名
func rectangleInfo(length, width float64) (area, perimeter float64) {
    area = length * width
    perimeter = 2 * (length + width)
    return // 自动返回命名的返回值
}

可变参数

GO
// 可变参数:使用 ...Type 接收任意数量参数
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

匿名函数与闭包

GO
// 匿名函数赋值给变量
greet := func(name string) string {
    return "你好, " + name
}

// 立即执行函数
func() {
    fmt.Println("立即执行")
}()

// 闭包:捕获外部变量
func counter() func() int {
    count := 0
    return func() int {
        count++ // 捕获外部的 count 变量
        return count
    }
}

init 函数

GO
// init 函数:程序启动时自动执行,无需手动调用
// 每个包可以有多个 init 函数
func init() {
    fmt.Println("init 函数执行")
}

defer

GO
// defer:延迟执行,函数返回前按 LIFO(后进先出)顺序执行
func readFile(filename string) {
    f, _ := os.Open(filename)
    defer f.Close() // 函数结束前关闭文件
    // ... 读取文件
}
💡 提示:Go语言中,函数参数都是值传递。切片、map、channel、指针等传递的是引用的副本,但指向的数据是共享的。

💡 提示:defer 语句的参数在声明时就会被求值,而不是在执行时求值。这是一个常见的陷阱。

💡 提示:init 函数不能被其他函数调用,它会在 main 函数之前自动执行。如果有多个 init,按源文件字母顺序执行。


3. 基本语法/用法(示例)

示例 1:基础用法(难度⭐)

目标: 学习基本的函数定义、调用和多返回值。

GO
package main

import (
    "errors"
    "fmt"
)

// greet 接收一个名字,返回问候语
func greet(name string) string {
    return "你好, " + name + "!"
}

// add 返回两个整数的和
func add(a, b int) int {
    return a + b
}

// swap 交换两个字符串(演示多返回值)
func swap(a, b string) (string, string) {
    return b, a
}

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

func main() {
    // 调用单返回值函数
    fmt.Println(greet("小明")) // 输出: 你好, 小明!
    fmt.Println(add(3, 5))     // 输出: 8

    // 调用多返回值函数
    x, y := swap("hello", "world")
    fmt.Println(x, y) // 输出: world hello

    // 调用带错误处理的函数
    result, err := divide(10, 3)
    if err != nil {
        fmt.Println("错误:", err)
    } else {
        fmt.Printf("10 / 3 = %.2f\n", result) // 输出: 10 / 3 = 3.33
    }

    // 测试除零错误
    _, err = divide(10, 0)
    if err != nil {
        fmt.Println("错误:", err) // 输出: 错误: 除数不能为零
    }
}
▶ 试一试

运行结果:

TEXT
你好, 小明!
8
world hello
10 / 3 = 3.33
错误: 除数不能为零

要点:


示例 2:进阶用法(难度⭐⭐)

目标: 学习命名返回值、可变参数、匿名函数和闭包。

GO
package main

import "fmt"

// ============ 命名返回值 ============

// rectangle 计算矩形的面积和周长(使用命名返回值)
func rectangle(length, width float64) (area, perimeter float64) {
    area = length * width           // 直接使用命名变量
    perimeter = 2 * (length + width)
    return // 自动返回命名的返回值,无需写 return area, perimeter
}

// ============ 可变参数 ============

// sum 计算任意个整数的和
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

// average 计算平均值,演示可变参数与其他参数混用
func average(first float64, rest ...float64) float64 {
    total := first
    count := 1.0
    for _, v := range rest {
        total += v
        count++
    }
    return total / count
}

// ============ 匿名函数 ============

// apply 对整数应用一个函数,返回结果
func apply(n int, f func(int) int) int {
    return f(n)
}

func main() {
    // 命名返回值
    area, perimeter := rectangle(5, 3)
    fmt.Printf("面积: %.1f, 周长: %.1f\n", area, perimeter)
    // 输出: 面积: 15.0, 周长: 16.0

    // 可变参数
    fmt.Println("sum(1,2,3):", sum(1, 2, 3))           // 6
    fmt.Println("sum(1,2,3,4,5):", sum(1, 2, 3, 4, 5)) // 15

    // 可变参数:传递切片
    nums := []int{10, 20, 30}
    fmt.Println("sum(nums...):", sum(nums...)) // 60(使用 ... 展开切片)

    // 可变参数与其他参数混用
    fmt.Printf("average: %.2f\n", average(10, 20, 30)) // 20.00

    // 匿名函数作为参数
    double := func(n int) int { return n * 2 }
    square := func(n int) int { return n * n }

    fmt.Println("apply(5, double):", apply(5, double)) // 10
    fmt.Println("apply(5, square):", apply(5, square)) // 25

    // 立即执行匿名函数
    msg := "你好"
    func(m string) {
        fmt.Println(m)
    }(msg)

    // ============ 闭包 ============

    // counter 返回一个计数器闭包
    counter := func() func() int {
        count := 0
        return func() int {
            count++ // 捕获外部变量 count
            return count
        }
    }()

    fmt.Println("counter:", counter()) // 1
    fmt.Println("counter:", counter()) // 2
    fmt.Println("counter:", counter()) // 3

    // 闭包实现累加器
    adder := func() func(int) int {
        sum := 0
        return func(n int) int {
            sum += n
            return sum
        }
    }()

    fmt.Println("adder(10):", adder(10)) // 10
    fmt.Println("adder(20):", adder(20)) // 30
    fmt.Println("adder(30):", adder(30)) // 60
}
▶ 试一试

运行结果:

TEXT
面积: 15.0, 周长: 16.0
sum(1,2,3): 6
sum(1,2,3,4,5): 15
sum(nums...): 60
average: 20.00
apply(5, double): 10
apply(5, square): 25
你好
counter: 1
counter: 2
counter: 3
adder(10): 10
adder(20): 30
adder(30): 60

要点:


示例 3:综合应用(难度⭐⭐⭐)

目标: 实际项目中的函数设计模式:函数选项模式、中间件链、defer与错误处理。

GO
package main

import (
    "fmt"
    "strings"
    "time"
)

// ============ 函数选项模式 ============

// Server 服务器配置
type Server struct {
    host    string
    port    int
    timeout time.Duration
    maxConn int
}

// Option 配置选项函数类型
type Option func(*Server)

// WithHost 设置主机地址
func WithHost(host string) Option {
    return func(s *Server) {
        s.host = host
    }
}

// WithPort 设置端口
func WithPort(port int) Option {
    return func(s *Server) {
        s.port = port
    }
}

// WithTimeout 设置超时时间
func WithTimeout(timeout time.Duration) Option {
    return func(s *Server) {
        s.timeout = timeout
    }
}

// WithMaxConn 设置最大连接数
func WithMaxConn(max int) Option {
    return func(s *Server) {
        s.maxConn = max
    }
}

// NewServer 使用选项模式创建服务器
func NewServer(opts ...Option) *Server {
    // 默认配置
    s := &Server{
        host:    "localhost",
        port:    8080,
        timeout: 30 * time.Second,
        maxConn: 100,
    }
    // 应用所有选项
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// String 实现 Stringer 接口
func (s *Server) String() string {
    return fmt.Sprintf("Server{host:%s, port:%d, timeout:%v, maxConn:%d}",
        s.host, s.port, s.timeout, s.maxConn)
}

// ============ 中间件链模式 ============

// Handler 处理函数类型
type Handler func(string) string

// Middleware 中间件类型
type Middleware func(Handler) Handler

// loggingMiddleware 日志中间件
func loggingMiddleware(next Handler) Handler {
    return func(input string) string {
        fmt.Printf("[日志] 输入: %s\n", input)
        result := next(input)
        fmt.Printf("[日志] 输出: %s\n", result)
        return result
    }
}

// upperMiddleware 转大写中间件
func upperMiddleware(next Handler) Handler {
    return func(input string) string {
        return next(strings.ToUpper(input))
    }
}

// wrapMiddleware 包裹中间件
func wrapMiddleware(next Handler) Handler {
    return func(input string) string {
        return "【" + next(input) + "】"
    }
}

// chain 将多个中间件组合成链
func chain(handler Handler, middlewares ...Middleware) Handler {
    // 从后向前包裹,确保第一个中间件最先执行
    for i := len(middlewares) - 1; i >= 0; i-- {
        handler = middlewares[i](handler)
    }
    return handler
}

// ============ defer 实战:资源清理与错误恢复 ============

// simulateDB 模拟数据库操作
func simulateDB() (err error) {
    fmt.Println("1. 获取数据库连接")

    // defer 按 LIFO 顺序执行
    defer fmt.Println("5. 释放数据库连接(最后执行)")
    defer fmt.Println("4. 记录操作日志")
    defer fmt.Println("3. 关闭游标")

    fmt.Println("2. 执行查询")

    // 模拟 panic 恢复
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("  [恢复] 捕获 panic: %v\n", r)
            err = fmt.Errorf("操作失败: %v", r)
        }
    }()

    // 模拟一个 panic
    panic("连接超时")

    // 后面的代码不会执行
    // fmt.Println("查询完成")
    // return nil
}

func main() {
    // ===== 函数选项模式 =====
    fmt.Println("=== 函数选项模式 ===")

    // 使用默认配置
    s1 := NewServer()
    fmt.Println(s1)

    // 自定义配置
    s2 := NewServer(
        WithHost("0.0.0.0"),
        WithPort(9090),
        WithTimeout(60*time.Second),
        WithMaxConn(500),
    )
    fmt.Println(s2)

    // ===== 中间件链 =====
    fmt.Println("\n=== 中间件链 ===")

    // 基础处理函数
    handler := func(s string) string {
        return "处理: " + s
    }

    // 组合中间件
    final := chain(handler, loggingMiddleware, upperMiddleware, wrapMiddleware)
    result := final("hello world")
    fmt.Println("最终结果:", result)

    // ===== defer 与错误恢复 =====
    fmt.Println("\n=== defer 执行顺序 ===")
    err := simulateDB()
    if err != nil {
        fmt.Println("主函数捕获错误:", err)
    }
}
▶ 试一试

运行结果:

TEXT
=== 函数选项模式 ===
Server{host:localhost, port:8080, timeout:30s, maxConn:100}
Server{host:0.0.0.0, port:9090, timeout:1m0s, maxConn:500}

=== 中间件链 ===
[日志] 输入: HELLO WORLD
[日志] 输出: 【处理: HELLO WORLD】
最终结果: 【处理: HELLO WORLD】

=== defer 执行顺序 ===
1. 获取数据库连接
2. 执行查询
  [恢复] 捕获 panic: 连接超时
3. 关闭游标
4. 记录操作日志
5. 释放数据库连接(最后执行)
主函数捕获错误: 操作失败: 连接超时

要点:


3. 常见应用场景

场景一:错误处理封装

在实际项目中,经常需要封装统一的错误处理逻辑:

GO
package main

import (
    "fmt"
    "strconv"
)

// Result 统一结果封装
type Result struct {
    Data    interface{}
    Err     error
}

// parseJSON 模拟JSON解析,返回结果和错误
func parseJSON(jsonStr string) (map[string]interface{}, error) {
    // 简化演示:实际项目中使用 encoding/json
    if jsonStr == "" {
        return nil, fmt.Errorf("JSON字符串不能为空")
    }
    return map[string]interface{}{"key": "value"}, nil
}

// parseInt 安全地将字符串转为整数
func parseInt(s string) (int, error) {
    n, err := strconv.Atoi(s)
    if err != nil {
        return 0, fmt.Errorf("解析整数失败 '%s': %w", s, err)
    }
    return n, nil
}

// withRetry 重试函数
func withRetry(attempts int, f func() error) error {
    var err error
    for i := 0; i < attempts; i++ {
        err = f()
        if err == nil {
            return nil
        }
        fmt.Printf("  第%d次尝试失败: %v\n", i+1, err)
    }
    return fmt.Errorf("重试%d次后失败: %w", attempts, err)
}

func main() {
    // 错误处理
    result, err := parseJSON(`{"name": "test"}`)
    if err != nil {
        fmt.Println("解析错误:", err)
        return
    }
    fmt.Println("解析结果:", result)

    // 安全类型转换
    num, err := parseInt("123")
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println("转换结果:", num)

    // 重试机制
    attempt := 0
    err = withRetry(3, func() error {
        attempt++
        if attempt < 3 {
            return fmt.Errorf("模拟失败")
        }
        return nil
    })
    if err != nil {
        fmt.Println("最终失败:", err)
    } else {
        fmt.Println("最终成功!")
    }
}

场景二:函数作为数据处理管道

GO
package main

import (
    "fmt"
    "strings"
)

// Transform 数据转换函数类型
type Transform func(string) string

// Pipeline 数据处理管道
type Pipeline struct {
    steps []Transform
}

// NewPipeline 创建新的管道
func NewPipeline() *Pipeline {
    return &Pipeline{}
}

// Add 添加处理步骤
func (p *Pipeline) Add(steps ...Transform) *Pipeline {
    p.steps = append(p.steps, steps...)
    return p
}

// Execute 执行管道
func (p *Pipeline) Execute(input string) string {
    result := input
    for _, step := range p.steps {
        result = step(result)
    }
    return result
}

func main() {
    // 定义转换函数
    trim := func(s string) string { return strings.TrimSpace(s) }
    lower := func(s string) string { return strings.ToLower(s) }
    replace := func(s string) string { return strings.ReplaceAll(s, " ", "-") }
    prefix := func(s string) string { return "processed-" + s }

    // 构建处理管道
    pipeline := NewPipeline().
        Add(trim, lower, replace, prefix)

    // 执行管道
    result := pipeline.Execute("  Hello World Go  ")
    fmt.Println(result) // 输出: processed-hello-world-go

    // 复用管道处理多个输入
    inputs := []string{"  Foo Bar  ", "  HELLO  ", "  Go Lang  "}
    for _, input := range inputs {
        fmt.Printf("%-20s => %s\n", input, pipeline.Execute(input))
    }
}

❓ 常见问题

Q1:函数参数是值传递还是引用传递?

A: Go语言中所有函数参数都是值传递。但切片、map、channel、指针等类型传递的是"引用的副本",修改它们指向的数据会影响原数据,但重新赋值不会影响原变量。

GO
package main

import "fmt"

// modifySlice 修改切片内容(会影响原切片)
func modifySlice(s []int) {
    s[0] = 999 // 修改元素,影响原切片
    fmt.Println("函数内:", s)
}

// reassignSlice 重新赋值切片(不影响原切片)
func reassignSlice(s []int) {
    s = append(s, 4, 5, 6) // append 可能创建新底层数组
    fmt.Println("函数内:", s)
}

func main() {
    nums := []int{1, 2, 3}

    modifySlice(nums)
    fmt.Println("函数外:", nums) // [999 2 3],原切片被修改

    reassignSlice(nums)
    fmt.Println("函数外:", nums) // [999 2 3],原切片未变
}

Q2:defer 的参数什么时候求值?

A: defer 语句的参数在声明时就会被求值,而不是在执行时求值。

GO
package main

import "fmt"

func main() {
    x := 10

    // defer 的参数在声明时求值,此时 x = 10
    defer fmt.Println("defer 中 x =", x)

    x = 20
    fmt.Println("修改后 x =", x)

    // 如果需要在 defer 执行时求值,使用闭包
    defer func() {
        fmt.Println("闭包 defer 中 x =", x) // x = 20
    }()

    x = 30
}

输出:

TEXT
修改后 x = 20
闭包 defer 中 x = 30
defer 中 x = 10

Q3:一个包可以有多个 init 函数吗?执行顺序是什么?

A: 可以。一个包(甚至一个文件)可以有多个 init 函数。执行顺序:

  1. 首先执行被导入包的 init 函数
  2. 同一包内,按源文件名字母顺序执行
  3. 同一文件内,按 init 函数出现的顺序执行
GO
package main

import "fmt"

func init() {
    fmt.Println("第一个 init")
}

func init() {
    fmt.Println("第二个 init")
}

func main() {
    fmt.Println("main 函数")
}

输出:

TEXT
第一个 init
第二个 init
main 函数

Q4:如何实现可选参数?

A: Go语言不直接支持可选参数,常用以下三种方式实现:

GO
package main

import "fmt"

// 方式1:使用结构体
type Config struct {
    Host string
    Port int
    Debug bool
}

func connect(cfg Config) {
    fmt.Printf("连接到 %s:%d (debug=%v)\n", cfg.Host, cfg.Port, cfg.Debug)
}

// 方式2:使用函数选项模式(推荐)
type Option func(*Config)

func WithHost(host string) Option {
    return func(c *Config) { c.Host = host }
}

func WithPort(port int) Option {
    return func(c *Config) { c.Port = port }
}

func WithDebug(debug bool) Option {
    return func(c *Config) { c.Debug = debug }
}

func newConnect(opts ...Option) {
    cfg := &Config{Host: "localhost", Port: 8080, Debug: false}
    for _, opt := range opts {
        opt(cfg)
    }
    fmt.Printf("连接到 %s:%d (debug=%v)\n", cfg.Host, cfg.Port, cfg.Debug)
}

func main() {
    // 结构体方式
    connect(Config{Host: "127.0.0.1", Port: 3306})

    // 选项模式
    newConnect()
    newConnect(WithHost("127.0.0.1"), WithPort(3306), WithDebug(true))
}

📖 小节


📝 作业

练习 1(⭐):基础函数练习

要求: 编写以下函数并测试:

  1. max(a, b int) int — 返回两个整数中较大的一个
  2. isEven(n int) bool — 判断一个整数是否为偶数
  3. swap(a, b *int) — 使用指针交换两个整数的值
GO
// 参考框架
package main

import "fmt"

func max(a, b int) int {
    // 在此实现
    return 0
}

func isEven(n int) bool {
    // 在此实现
    return false
}

func swap(a, b *int) {
    // 在此实现(提示:使用指针)
}

func main() {
    fmt.Println(max(3, 5))     // 应输出: 5
    fmt.Println(isEven(4))     // 应输出: true
    fmt.Println(isEven(7))     // 应输出: false

    x, y := 10, 20
    swap(&x, &y)
    fmt.Println(x, y)          // 应输出: 20 10
}

练习 2(⭐⭐):可变参数与闭包

要求:

  1. 编写 filter(nums []int, predicate func(int) bool) []int — 过滤切片中满足条件的元素
  2. 编写 makeMultiplier(factor int) func(int) int — 返回一个乘法闭包
  3. 使用 filter 和闭包,从切片中筛选出所有偶数
GO
// 参考框架
package main

import "fmt"

func filter(nums []int, predicate func(int) bool) []int {
    // 在此实现:遍历 nums,将满足 predicate 的元素加入结果切片
    return nil
}

func makeMultiplier(factor int) func(int) int {
    // 在此实现:返回一个闭包,将输入乘以 factor
    return nil
}

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

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

    // 筛选大于5的数
    big := filter(nums, func(n int) bool {
        return n > 5
    })
    fmt.Println("大于5:", big) // [6 7 8 9 10]

    // 使用闭包创建乘法器
    double := makeMultiplier(2)
    triple := makeMultiplier(3)

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

练习 3(⭐⭐⭐):综合实战——计算器与命令模式

要求: 实现一个支持注册命令的计算器系统:

  1. 定义 Calculator 结构体,包含一个命令映射 map[string]func([]int) int
  2. 实现 Register(name string, op func([]int) int) 方法注册命令
  3. 实现 Execute(name string, args []int) (int, error) 方法执行命令
  4. 注册以下命令:add(求和)、mul(求积)、max(最大值)
  5. 使用 init 函数自动注册所有命令
GO
// 参考框架
package main

import (
    "errors"
    "fmt"
)

type Calculator struct {
    // 在此定义字段
}

func NewCalculator() *Calculator {
    // 在此实现
    return nil
}

func (c *Calculator) Register(name string, op func([]int) int) {
    // 在此实现
}

func (c *Calculator) Execute(name string, args []int) (int, error) {
    // 在此实现:查找命令并执行,不存在则返回错误
    return 0, nil
}

func (c *Calculator) ListCommands() []string {
    // 在此实现:返回所有已注册的命令名
    return nil
}

func main() {
    calc := NewCalculator()

    // 注册命令
    calc.Register("add", func(args []int) int {
        sum := 0
        for _, v := range args {
            sum += v
        }
        return sum
    })

    calc.Register("mul", func(args []int) int {
        product := 1
        for _, v := range args {
            product *= v
        }
        return product
    })

    calc.Register("max", func(args []int) int {
        if len(args) == 0 {
            return 0
        }
        m := args[0]
        for _, v := range args[1:] {
            if v > m {
                m = v
            }
        }
        return m
    })

    // 测试
    tests := []struct {
        cmd  string
        args []int
    }{
        {"add", []int{1, 2, 3, 4, 5}},
        {"mul", []int{2, 3, 4}},
        {"max", []int{10, 3, 7, 9, 1}},
    }

    for _, t := range tests {
        result, err := calc.Execute(t.cmd, t.args)
        if err != nil {
            fmt.Printf("错误: %v\n", err)
        } else {
            fmt.Printf("%s(%v) = %d\n", t.cmd, t.args, result)
        }
    }

    // 列出所有命令
    fmt.Println("可用命令:", calc.ListCommands())

    // 测试不存在的命令
    _, err := calc.Execute("sqrt", []int{16})
    if err != nil {
        fmt.Println("错误:", err)
    }
}

预期输出:

TEXT
add([1 2 3 4 5]) = 15
mul([2 3 4]) = 24
max([10 3 7 9 1]) = 10
可用命令: [add mul max]
错误: 未知命令: sqrt

下一课

下一课:数组与切片 →

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏