Go 语言从入门到进阶:5. 玩转Go函数

玩转Go函数:从基础到进阶,这一篇就够了!

大家好!今天我们来聊聊Go语言中的函数。函数是Go程序的基础构建块,理解透彻函数的使用,你的Go编程之路就成功了一半!

一、函数基础:Hello, World! 的进阶版

先看一个最简单的函数:

go 复制代码
package main

import "fmt"

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

func main() {
    sayHello()  // 调用函数
}

二、参数传递:值传递是主流

Go语言函数参数默认是值传递,这意味着函数内部操作的是参数的副本:

go 复制代码
func modifyValue(x int) {
    x = 100  // 修改的是副本,不影响原值
}

func main() {
    a := 10
    modifyValue(a)
    fmt.Println(a)  // 输出: 10,原值没变!
}

想修改原值?用指针!

go 复制代码
func modifyPointer(x *int) {
    *x = 100  // 通过指针修改原值
}

func main() {
    a := 10
    modifyPointer(&a)
    fmt.Println(a)  // 输出: 100
}

三、多返回值:Go的特色亮点

Go函数可以返回多个值,这是我最喜欢的特性之一:

go 复制代码
// 返回商和余数
func divide(a, b int) (int, int) {
    quotient := a / b
    remainder := a % b
    return quotient, remainder
}

// 实际开发中常用于返回结果和错误
func getUser(id int) (string, error) {
    if id <= 0 {
        return "", fmt.Errorf("无效的用户ID: %d", id)
    }
    return "Alice", nil
}

func main() {
    q, r := divide(10, 3)
    fmt.Printf("商: %d, 余数: %d\n", q, r)  // 商: 3, 余数: 1
    
    name, err := getUser(1)
    if err != nil {
        fmt.Println("错误:", err)
    } else {
        fmt.Println("用户:", name)
    }
}

四、命名返回值:让代码更清晰

可以为返回值命名,这样函数体内可以直接使用:

go 复制代码
func calculate(x, y int) (sum int, product int) {
    sum = x + y      // 直接使用命名返回值
    product = x * y
    return           // 裸返回,返回sum和product
}

func main() {
    s, p := calculate(5, 3)
    fmt.Printf("和: %d, 积: %d\n", s, p)  // 和: 8, 积: 15
}

小贴士:命名返回值虽好,但不要过度使用。简单函数用裸返回很清晰,复杂函数建议显式return。

五、可变参数:处理不定数量的参数

go 复制代码
// ...int 表示可以接收任意数量的int参数
func sum(nums ...int) int {
    total := 0
    for _, num := range nums {
        total += num
    }
    return total
}

func main() {
    fmt.Println(sum(1, 2, 3))        // 6
    fmt.Println(sum(10, 20, 30, 40)) // 100
    
    // 将切片展开传入
    numbers := []int{1, 2, 3, 4, 5}
    fmt.Println(sum(numbers...))     // 15
}

六、函数作为值:一等公民

Go中函数是第一类公民,可以赋值给变量、作为参数传递:

go 复制代码
// 函数作为参数
func apply(nums []int, fn func(int) int) []int {
    result := make([]int, len(nums))
    for i, v := range nums {
        result[i] = fn(v)
    }
    return result
}

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

func main() {
    // 函数作为参数
    numbers := []int{1, 2, 3, 4}
    doubled := apply(numbers, func(x int) int {
        return x * 2
    })
    fmt.Println(doubled)  // [2 4 6 8]
    
    // 函数作为返回值(闭包)
    double := makeMultiplier(2)
    triple := makeMultiplier(3)
    fmt.Println(double(5))  // 10
    fmt.Println(triple(5))  // 15
}

七、匿名函数与闭包:Go的魔法

go 复制代码
func main() {
    // 立即执行的匿名函数
    result := func(x, y int) int {
        return x + y
    }(3, 4)
    fmt.Println(result)  // 7
    
    // 闭包:捕获外部变量
    counter := func() func() int {
        count := 0
        return func() int {
            count++
            return count
        }
    }()
    
    fmt.Println(counter())  // 1
    fmt.Println(counter())  // 2
    fmt.Println(counter())  // 3
}

八、defer:延迟执行,资源管理的利器

defer确保函数返回前执行某个操作,常用于资源释放:

go 复制代码
func readFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()  // 确保文件会被关闭
    
    // 处理文件内容...
    
    return nil  // file.Close() 会自动执行
}

// defer的执行顺序是LIFO(后进先出)
func demoDefer() {
    defer fmt.Println("第一个defer")
    defer fmt.Println("第二个defer")
    defer fmt.Println("第三个defer")
    fmt.Println("正常执行")
}
// 输出:
// 正常执行
// 第三个defer
// 第二个defer
// 第一个defer

九、实际案例:一个简单的缓存函数

go 复制代码
package main

import (
    "fmt"
    "time"
)

// 带缓存的函数调用
func memoize(fn func(int) int) func(int) int {
    cache := make(map[int]int)
    return func(x int) int {
        if val, exists := cache[x]; exists {
            fmt.Printf("缓存命中: %d -> %d\n", x, val)
            return val
        }
        result := fn(x)
        cache[x] = result
        fmt.Printf("计算并缓存: %d -> %d\n", x, result)
        return result
    }
}

// 耗时计算函数
func slowSquare(n int) int {
    time.Sleep(time.Second) // 模拟耗时操作
    return n * n
}

func main() {
    cachedSquare := memoize(slowSquare)
    
    fmt.Println(cachedSquare(5))  // 计算并缓存: 5 -> 25
    fmt.Println(cachedSquare(5))  // 缓存命中: 5 -> 25
    fmt.Println(cachedSquare(10)) // 计算并缓存: 10 -> 100
}

十、最佳实践总结

  1. 函数命名:使用驼峰式,首字母大写表示公开,小写表示私有
  2. 单一职责:一个函数只做一件事
  3. 早返回:减少嵌套,提高可读性
  4. 错误处理:Go没有异常,要显式处理每个错误
  5. 函数长度:建议不超过50行,过长就要考虑拆分
  6. 参数数量:超过4个参数考虑使用结构体
go 复制代码
// 好的实践
func processUser(user User, options Options) error {
    if err := validate(user); err != nil {
        return err  // 早返回
    }
    // 主要逻辑...
    return nil
}

// 避免这样
func badFunc(a, b, c, d, e, f int, g, h string, i bool) error {
    // 参数太多了!
}

写在最后

Go语言的函数设计简洁而强大。掌握这些特性,你就能写出优雅高效的Go代码。记住:大道至简是Go的设计哲学,函数设计也不例外。

*

相关推荐
多彩电脑1 小时前
Kivy如何自定义事件
开发语言·python
java_cj1 小时前
LangChain初入门 - 简化LLM开发难度的利器
开发语言·python·langchain
sleven fung1 小时前
llama-cpp-python 本地部署入门
开发语言·python·算法·llama
头歌实践平台1 小时前
C++面向对象 - 运算符重载的应用
开发语言·c++·算法
福大大架构师每日一题1 小时前
rust 1.96.0 更新:语言、编译器、Cargo、Rustdoc、兼容性全面升级,必看完整解读
android·开发语言·rust
思麟呀1 小时前
C++11并发编程:互斥锁
linux·开发语言·c++·windows
砍材农夫1 小时前
物联网实战:Spring Boot + Netty 搭建 MQTT | MQTT 设备模拟器
java·spring boot·后端·物联网·struts·spring·netty
郭涤生1 小时前
C++ 各类数据的内存分区与读写性能详解
开发语言·c++