18 KiB
18 KiB
第四章:函数与接口 —— 代码复用与多态
本章目标:深入理解 Go 的函数式编程特性(闭包、匿名函数)、defer 机制、错误处理、panic/recover,以及接口的底层实现原理和最佳实践。
4.1 函数基础回顾与深度解析
4.1.1 函数声明与调用
package main
import "fmt"
// 基本函数
func add(a int, b int) int {
return a + b
}
// 省略参数类型(连续同类型)
func addShort(a, b int) int {
return a + b
}
// 多个返回值
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("除数不能为零")
}
return a / b, nil
}
// 命名返回值
func divideNamed(a, b float64) (result float64, err error) {
if b == 0 {
err = fmt.Errorf("除数不能为零")
return // 返回命名返回值
}
result = a / b
return // 返回命名返回值
}
func main() {
fmt.Println(add(3, 4))
fmt.Println(addShort(3, 4))
result, err := divide(10, 2)
if err != nil {
fmt.Println("错误:", err)
} else {
fmt.Printf("10 / 2 = %.2f\n", result)
}
result2, _ := divideNamed(20, 4)
fmt.Printf("20 / 4 = %.2f\n", result2)
}
深度解析:
- 命名返回值:适合返回值较多或需要明确语义的场景
- 省略类型:仅适用于连续的同类型参数
- 多返回值:Go 的特色,常用于返回结果 + 错误
4.1.2 可变参数
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
func printArgs(prefix string, args ...interface{}) {
fmt.Printf("%s: ", prefix)
for _, arg := range args {
fmt.Printf("%v ", arg)
}
fmt.Println()
}
func main() {
fmt.Println(sum(1, 2, 3, 4, 5)) // 15
fmt.Println(sum()) // 0
// 切片展开
nums := []int{10, 20, 30}
fmt.Println(sum(nums...)) // 60
printArgs("参数", 1, "hello", 3.14, true)
}
深度解析:
...T表示可变参数,在函数内部被视为切片- 调用时可以用
...展开切片 - 可变参数必须是最后一个参数
4.2 匿名函数与闭包
4.2.1 匿名函数
func anonymousFunc() {
// 定义匿名函数
func() {
fmt.Println("这是一个匿名函数")
}() // 立即调用
// 赋值给变量
multiply := func(a, b int) int {
return a * b
}
fmt.Println(multiply(3, 4)) // 12
// 作为参数传递
apply := func(f func(int, int) int, x, y int) int {
return f(x, y)
}
fmt.Println(apply(add, 5, 6)) // 11
}
4.2.2 闭包(Closure)⭐ 核心重点
闭包是引用了外部变量的匿名函数。闭包不仅包含函数本身,还包含其创建时的环境。
func closureExample() {
// 创建计数器
counter := func() func() int {
count := 0
return func() int {
count++
return count
}
}
c1 := counter()
c2 := counter()
fmt.Println(c1()) // 1
fmt.Println(c1()) // 2
fmt.Println(c1()) // 3
fmt.Println(c2()) // 1(独立的 count)
fmt.Println(c1()) // 4
}
深度解析:
- 闭包捕获变量而非值:内部函数引用的是外部变量的引用
- 每次调用
counter()都会创建新的count变量 - 闭包的生命周期长于外部函数
4.2.3 闭包的陷阱
陷阱 1:循环中的闭包
func closureLoopBug() {
funcs := make([]func(), 3)
for i := 0; i < 3; i++ {
funcs[i] = func() {
fmt.Println(i) // 捕获的是 i 的引用
}
}
for _, f := range funcs {
f() // 输出:3 3 3(循环结束后的 i 值)
}
}
// 正确做法
func closureLoopFix() {
funcs := make([]func(), 3)
for i := 0; i < 3; i++ {
i := i // 创建新的变量
funcs[i] = func() {
fmt.Println(i)
}
}
for _, f := range funcs {
f() // 输出:0 1 2
}
}
// 或者使用参数
func closureLoopFix2() {
funcs := make([]func(), 3)
for i := 0; i < 3; i++ {
funcs[i] = func(n int) {
fmt.Println(n)
}(i)
}
for _, f := range funcs {
f() // 输出:0 1 2
}
}
陷阱 2:闭包内存泄漏
func closureMemoryLeak() {
largeData := make([]byte, 1024*1024) // 1MB
smallFunc := func() {
fmt.Println(len(largeData))
}
// 即使 largeData 不再需要,只要 smallFunc 存在,largeData 就不会被回收
_ = smallFunc
}
最佳实践:
- 如果闭包不需要引用大对象,避免捕获
- 使用
func(n int)参数传递值而非捕获引用
4.2.4 闭包的实战应用
1. 工厂函数
func createMultiplier(factor int) func(int) int {
return func(x int) int {
return x * factor
}
}
func main() {
double := createMultiplier(2)
triple := createMultiplier(3)
fmt.Println(double(5)) // 10
fmt.Println(triple(5)) // 15
}
2. 中间件模式
type Handler func(string) string
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
}
}
func process(input string) string {
return "处理完成:" + input
}
func main() {
handler := loggingMiddleware(process)
handler("测试数据")
}
4.3 defer 机制
4.3.1 基本用法
func deferExample() {
fmt.Println("开始")
defer fmt.Println("延迟 1")
defer fmt.Println("延迟 2")
fmt.Println("结束")
}
// 输出:
// 开始
// 结束
// 延迟 2
// 延迟 1
深度解析:
defer语句立即执行,但函数调用延迟到外层函数返回前- 多个
defer按后进先出(LIFO) 顺序执行 - 常用于资源清理(文件关闭、锁释放、数据库连接)
4.3.2 defer 参数求值时机
func deferArgs() {
i := 0
defer fmt.Println("i =", i) // i 在 defer 时求值
i = 10
// 输出:i = 0
}
func deferArgs2() {
i := 0
defer func() {
fmt.Println("i =", i) // i 在函数执行时求值
}()
i = 10
// 输出:i = 10
}
深度解析:
- 普通函数调用的参数在
defer时立即求值 - 闭包的变量在闭包执行时求值
4.3.3 defer 的返回值修改
func deferReturn() (result int) {
defer func() {
result += 10 // 修改命名返回值
}()
result = 5
return // 返回 15
}
func deferReturn2() int {
defer func() {
// 无法修改返回值(无名)
}()
return 5
}
4.3.4 常见陷阱
陷阱 1:在循环中使用 defer
func deferLoopBug() {
for i := 0; i < 10; i++ {
f, _ := os.Open("file.txt")
defer f.Close() // 所有 defer 在函数结束时执行
// 文件描述符可能耗尽
}
}
// 正确做法
func deferLoopFix() {
for i := 0; i < 10; i++ {
func() {
f, _ := os.Open("file.txt")
defer f.Close()
// 使用 f
}() // 立即返回,defer 执行
}
}
陷阱 2:defer 掩盖错误
func deferError() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
panic("出错了")
return nil
}
4.4 panic 与 recover
4.4.1 panic 基础
func panicExample() {
defer func() {
if r := recover(); r != nil {
fmt.Println("捕获 panic:", r)
}
}()
fmt.Println("开始")
panic("发生错误")
fmt.Println("不会执行")
}
深度解析:
panic立即停止当前函数,开始栈展开recover必须在defer中调用才能捕获 panic- panic 会终止程序,除非被 recover 捕获
4.4.2 使用场景
1. 不可恢复的错误
func divide(a, b int) int {
if b == 0 {
panic("除数不能为零") // 编程错误,应该避免
}
return a / b
}
// 正确做法:返回错误
func divideSafe(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("除数不能为零")
}
return a / b, nil
}
2. 初始化失败
var config *Config
func init() {
c, err := loadConfig()
if err != nil {
panic(fmt.Sprintf("加载配置失败:%v", err))
}
config = c
}
4.4.3 自定义 panic 类型
type MyError struct {
Code int
Message string
}
func (e MyError) Error() string {
return fmt.Sprintf("错误码 %d: %s", e.Code, e.Message)
}
func safeDivide(a, b int) (int, error) {
if b == 0 {
panic(MyError{Code: 1001, Message: "除数不能为零"})
}
return a / b, nil
}
func handlePanic() {
defer func() {
if r := recover(); r != nil {
if err, ok := r.(MyError); ok {
fmt.Printf("自定义错误:%v\n", err)
} else {
fmt.Printf("未知 panic: %v\n", r)
}
}
}()
safeDivide(10, 0)
}
4.5 接口(Interfaces)
4.5.1 接口定义与实现
type Speaker interface {
Speak() string
}
type Dog struct {
Name string
}
func (d Dog) Speak() string {
return d.Name + " 汪汪叫"
}
type Cat struct {
Name string
}
func (c Cat) Speak() string {
return c.Name + " 喵喵叫"
}
func makeSound(s Speaker) {
fmt.Println(s.Speak())
}
func main() {
dog := Dog{Name: "旺财"}
cat := Cat{Name: "咪咪"}
makeSound(dog) // 旺财 汪汪叫
makeSound(cat) // 咪咪 喵喵叫
}
深度解析:
- 接口是隐式实现:不需要
implements关键字 - 只要类型实现了接口的所有方法,就自动实现该接口
- 接口变量可以存储任何实现该接口的类型
4.5.2 空接口
func printAny(v interface{}) {
fmt.Println(v)
}
// Go 1.18+ 可以使用 any(interface{}的别名)
func printAny2(v any) {
fmt.Println(v)
}
func main() {
printAny(1)
printAny("hello")
printAny(3.14)
printAny([]int{1, 2, 3})
}
4.5.3 类型断言
func typeAssertion() {
var v interface{} = "hello"
// 基本断言
s := v.(string)
fmt.Println(s)
// 安全断言
if n, ok := v.(int); ok {
fmt.Println(n)
} else {
fmt.Println("不是 int 类型")
}
// 类型开关
switch t := v.(type) {
case int:
fmt.Printf("整数:%d\n", t)
case string:
fmt.Printf("字符串:%s\n", t)
case float64:
fmt.Printf("浮点数:%.2f\n", t)
default:
fmt.Printf("未知类型:%T\n", t)
}
}
4.5.4 接口的底层实现
深度解析: Go 的接口在底层由两个字段组成:
data:指向实际数据的指针type:类型的描述信息(_type)
// 接口底层结构(伪代码)
type iface struct {
tab *itab // 接口表:包含类型和方法
data unsafe.Pointer
}
type itab struct {
inter *interfacetype // 接口类型
_type *_type // 具体类型
hash uint32 // 类型哈希
fun [1]unsafe.Pointer // 方法表
}
空接口(interface{}):
- 所有类型都实现空接口
- 底层是
eface结构
type eface struct {
_type *_type
data unsafe.Pointer
}
4.5.5 接口最佳实践
1. 小接口优于大接口
// 错误:接口太大
type BigInterface interface {
Read() []byte
Write([]byte) (int, error)
Close() error
Seek(int64, int) (int64, error)
// ... 很多方法
}
// 正确:拆分为小接口
type Reader interface {
Read() []byte
}
type Writer interface {
Write([]byte) (int, error)
}
type Closer interface {
Close() error
}
// 组合接口
type ReadWriter interface {
Reader
Writer
}
2. 接口定义在使用者一侧
// 错误:在定义者一侧定义接口
type Dog struct{}
func (d Dog) Speak() string { return "汪汪" }
// 在别处定义接口
type Speaker interface {
Speak() string
}
// 正确:在使用者一侧定义
func process(s Speaker) {
fmt.Println(s.Speak())
}
// Dog 自动实现 Speaker
3. 避免过度抽象
// 错误:不必要的接口
type Logger interface {
Log(string)
}
type FileLogger struct{}
func (f FileLogger) Log(msg string) {
fmt.Println("File:", msg)
}
func useLogger(l Logger) {
l.Log("test")
}
// 正确:直接使用具体类型(如果不需要多态)
func useLogger2(l FileLogger) {
l.Log("test")
}
4.6 深度实践:综合案例
4.6.1 插件系统
type Plugin interface {
Name() string
Run() error
}
type HelloPlugin struct{}
func (h HelloPlugin) Name() string {
return "HelloPlugin"
}
func (h HelloPlugin) Run() error {
fmt.Println("Hello from plugin!")
return nil
}
type MathPlugin struct{}
func (m MathPlugin) Name() string {
return "MathPlugin"
}
func (m MathPlugin) Run() error {
fmt.Printf("2 + 2 = %d\n", 2+2)
return nil
}
func loadPlugins() []Plugin {
return []Plugin{
HelloPlugin{},
MathPlugin{},
}
}
func main() {
plugins := loadPlugins()
for _, p := range plugins {
fmt.Printf("加载插件:%s\n", p.Name())
if err := p.Run(); err != nil {
fmt.Println("插件运行失败:", err)
}
}
}
4.6.2 中间件链
type Middleware func(Handler) Handler
type Handler func(string) string
func logging(next Handler) Handler {
return func(input string) string {
fmt.Printf("[LOG] 输入:%s\n", input)
result := next(input)
fmt.Printf("[LOG] 输出:%s\n", result)
return result
}
}
func timing(next Handler) Handler {
return func(input string) string {
start := time.Now()
result := next(input)
fmt.Printf("[TIME] 耗时:%v\n", time.Since(start))
return result
}
}
func auth(next Handler) Handler {
return func(input string) string {
if input == "" {
return "错误:空输入"
}
return next(input)
}
}
func businessLogic(input string) string {
return "处理结果:" + input
}
func main() {
// 构建中间件链
handler := auth(timing(logging(businessLogic)))
result := handler("测试数据")
fmt.Println("最终结果:", result)
}
4.6.3 错误处理链
type AppError struct {
Code int
Message string
Cause error
}
func (e *AppError) Error() string {
if e.Cause != nil {
return fmt.Sprintf("错误码 %d: %s (原因:%v)", e.Code, e.Message, e.Cause)
}
return fmt.Sprintf("错误码 %d: %s", e.Code, e.Message)
}
func wrapError(err error, code int, message string) error {
if err == nil {
return nil
}
return &AppError{
Code: code,
Message: message,
Cause: err,
}
}
func readFile(path string) ([]byte, error) {
// 模拟读取失败
return nil, fmt.Errorf("文件不存在")
}
func processFile(path string) error {
data, err := readFile(path)
if err != nil {
return wrapError(err, 1001, "读取文件失败")
}
// 处理数据
if len(data) == 0 {
return wrapError(nil, 1002, "文件为空")
}
return nil
}
func main() {
err := processFile("test.txt")
if err != nil {
if appErr, ok := err.(*AppError); ok {
fmt.Printf("应用错误:代码=%d, 消息=%s\n", appErr.Code, appErr.Message)
if appErr.Cause != nil {
fmt.Printf("根本原因:%v\n", appErr.Cause)
}
} else {
fmt.Println("未知错误:", err)
}
}
}
4.7 常见陷阱与最佳实践
4.7.1 闭包陷阱总结
- 循环变量捕获:循环中使用闭包时,变量会共享
- 内存泄漏:闭包捕获大对象导致无法回收
- 延迟求值:注意 defer 中参数的求值时机
4.7.2 defer 陷阱总结
- 循环中 defer:可能导致资源耗尽
- 返回值修改:只能修改命名返回值
- 掩盖错误:recover 可能掩盖真正的 bug
4.7.3 接口陷阱总结
- 空接口滥用:过度使用
interface{}失去类型安全 - 接口过大:定义太多方法的接口难以实现
- 值 vs 指针:值类型实现接口,指针类型不实现(反之亦然)
type S struct{}
func (s S) Method() {}
var _ interface{ Method() } = S{} // 正确
var _ interface{ Method() } = &S{} // 正确
// var _ interface{ Method() } = (*S)(nil) // 错误:nil 指针不实现
4.7.4 最佳实践
- 优先使用错误返回:panic 仅用于不可恢复错误
- 小接口:定义最小必要的接口方法
- 闭包谨慎:避免捕获大对象,循环中注意变量作用域
- defer 适度:仅在需要清理资源时使用
- 类型断言安全:使用
ok模式避免 panic
4.8 课后练习
- 闭包计数器:实现一个支持
increment()、decrement()、reset()的计数器 - 中间件框架:实现一个简单的 HTTP 中间件框架
- 错误包装:实现一个支持错误链包装的工具函数
- 接口组合:设计一组小接口,组合成复杂功能
- defer 测试:编写测试验证 defer 的执行顺序和参数求值时机
4.9 下一步
完成本章后,你将进入第五章:并发编程,深入学习 Go 最强大的特性——Goroutine 和 Channel,以及并发模式、同步原语和并发最佳实践。
代码仓库位置:https://giter.top/openclaw/test/tree/main/chapters/chapter-4
下一章预告:Goroutine 原理、Channel 通信模式、sync 包、并发设计模式、竞态检测