Go语言学习(三)

一、map作为参数

makemap :返回的是一个 *hmap(指向hmap结构体的指针)。

  • Map变量本身就是一个指针。

makeslice :返回的是一个 slice 结构体(包含array指针、lencap三个字段)。

  • Slice变量本身是一个包含指针的结构体,而不是一个指针。
Go 复制代码
package main

import "fmt"

func modifyMap(m map[string]int) {
	m["key"] = 100 // 这个操作会影响外部的原始map
	m = nil        // 这个操作不会影响外部的map变量
}

func main() {
	myMap := make(map[string]int)
	modifyMap(myMap)
	fmt.Println(myMap) // 输出:map[key:100]
}

# 输出
map[key:100]

myMap 是一个指针(假设指向地址 0x1234)。
调用 modifyMap(myMap) 时,将 0x1234 这个指针值复制一份传递给函数。
函数内的 m 是外部指针的副本,但它指向同一个hmap结构体。
通过 m["key"] = 100 修改的是 0x1234 地址处的map内容,因此外部可见。
m = nil 只是让函数内的副本指向nil,不影响外部的 myMap 指针。

slice作为参数,我在上一篇有讲过,下面列举一下区别:

特性 Map Slice
底层类型 *hmap(指针) slice(结构体)
函数传参 传递指针的副本 传递结构体的副本
修改元素 影响原始map 影响底层数组(如果未扩容)
修改长度 Map无此概念 不影响原始slice的len
修改容量 Map无此概念 不影响原始slice的cap
重新赋值 不影响 原始map变量(如m = nil 不影响 原始slice变量(如s = newSlice
扩容影响 自动处理,对使用者透明 可能指向新数组,与原始slice分离

这种设计差异是有其合理性的:

  1. Map的指针设计:Map的实现非常复杂,涉及哈希桶、扩容等机制。通过返回指针,可以确保所有的map操作都作用于同一个map实体,避免复制整个map结构的开销。

  2. Slice的结构体设计:Slice被设计为数组的视图或描述符。传递结构体副本是轻量级的(只复制三个机器字),并且这种设计允许对同一底层数组进行多个不同的视图操作(比如切片操作),而不会相互干扰。

二、go语言中的defer函数

defer 是 Go 语言中用于延迟执行函数调用的关键字,常用于资源释放、错误处理等场景。其核心特性是:defer 的函数会在当前函数返回前执行,无论函数是正常返回、抛出错误还是 panic。

(1)基础用法:延迟执行函数

defer 后的函数调用会被压入一个栈中,当前函数执行完毕前,按「后进先出(LIFO)」顺序执行。

Go 复制代码
package main

import "fmt"

func basicDefer() {
    fmt.Println("函数开始")
    
    // 延迟执行的函数
    defer fmt.Println("延迟执行 1")
    defer fmt.Println("延迟执行 2") // 后 defer 的先执行
    
    fmt.Println("函数结束")
}

func main() {
    basicDefer()
    // 输出顺序:
    // 函数开始
    // 函数结束
    // 延迟执行 2
    // 延迟执行 1
}
(2)defer 与函数参数:参数值在声明时确定
Go 复制代码
package main

import "fmt"

func deferParam() {
    x := 10
    // defer 函数的参数在声明时计算(此时 x=10)
    defer fmt.Printf("defer 执行:x=%d\n", x)
    
    x = 20 // 修改 x 不影响 defer 函数的参数
    fmt.Printf("函数内:x=%d\n", x)
}

func main() {
    deferParam()
    // 输出:
    // 函数内:x=20
    // defer 执行:x=10
}

defer 函数的参数会在 defer 语句压入栈时计算并缓存,而非执行时。

(3)defer 与返回值:可修改具名返回值

若函数返回值是具名的(有变量名),defer 函数可以修改该返回值(因为返回值在函数执行过程中已被声明)。

Go 复制代码
package main

import "fmt"

// 具名返回值:result
func deferReturn() (result int) {
    defer func() {
        result += 10 // 修改具名返回值
    }()
    return 5 // 实际返回 5 + 10 = 15
}

func main() {
    fmt.Println(deferReturn()) // 输出:15
}

函数返回过程是「先给返回值赋值,再执行 defer,最后返回」,因此 defer 可以修改具名返回值。

(4)defer 与匿名函数:捕获外部变量

defer 常与匿名函数配合使用,匿名函数可以访问并修改外部变量(注意变量作用域)。

Go 复制代码
package main

import "fmt"

func deferAnonymous() {
    x := 10
    // 匿名函数捕获外部变量 x(引用传递)
    defer func() {
        x += 5
        fmt.Printf("defer 内:x=%d\n", x) // x=105
    }()
    
    x = 100
    fmt.Printf("函数内:x=%d\n", x) // x=100
}

func main() {
    deferAnonymous()
    // 输出:
    // 函数内:x=100
    // defer 内:x=105
}

匿名函数捕获的是变量的引用,而非值,因此 defer 执行时会反映变量的最新值。

(5)defer 释放资源:文件 / 锁等场景

defer 确保资源释放操作(如关闭文件、解锁)在函数退出前执行,避免资源泄漏。

Go 复制代码
package main

import (
    "fmt"
    "os"
)

func deferResource() {
    // 打开文件
    file, err := os.Open("test.txt")
    if err != nil {
        fmt.Println("文件打开失败:", err)
        return
    }
    // 需正确打开文件之后,才可以使用下面的语句
    // 延迟关闭文件(无论函数是否正常执行,都会关闭)
    defer file.Close()
    
    // 操作文件(示例)
    fmt.Println("文件操作完成")
}

func main() {
    deferResource()
    // 输出:文件操作完成(文件会被自动关闭)
}

即使 file 操作过程中出现错误,defer file.Close() 仍会执行,确保文件描述符被释放。

(6)deferpanic:在 panic 后执行

panic 会终止函数执行,但在终止前会执行所有已声明的 defer 函数。

Go 复制代码
package main

import "fmt"

func deferPanic() {
    defer fmt.Println("defer 1:在 panic 后执行")
    defer fmt.Println("defer 2:在 panic 后执行")
    
    fmt.Println("执行到这里")
    panic("发生异常") // 触发 panic
    fmt.Println("这里不会执行") // 被 panic 跳过
}

func main() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("捕获 panic:", err)
        }
    }()
    
    deferPanic()
    // 输出:
    // 执行到这里
    // defer 2:在 panic 后执行
    // defer 1:在 panic 后执行
    // 捕获 panic:发生异常
}

panic 后,defer 函数仍会按栈顺序执行,可用于在崩溃前记录日志、释放资源等。

(7)deferrecover:捕获 panic 恢复执行

recover 必须在 defer 函数中使用,用于捕获 panic 并恢复程序执行。

Go 复制代码
package main

import "fmt"

func riskyOperation() {
    defer func() {
        // 捕获 panic
        if err := recover(); err != nil {
            fmt.Println("恢复程序:", err)
        }
    }()
    
    fmt.Println("执行危险操作")
    panic("操作失败") // 触发 panic
    fmt.Println("操作成功") // 不会执行
}

func main() {
    riskyOperation()
    fmt.Println("程序继续执行") // 被 recover 后,这里会执行
    // 输出:
    // 执行危险操作
    // 恢复程序:操作失败
    // 程序继续执行
}

recover 只能在 defer 中生效,若没有 deferpanic 会一直向上传播直至程序退出。

(8)defer 的性能影响:避免在循环中滥用
Go 复制代码
package main

import (
    "fmt"
    "time"
)

func deferPerformance() {
    // 循环中使用 defer(不推荐)
    start := time.Now()
    for i := 0; i < 1000000; i++ {
        defer func() {}() // 空的 defer 函数
    }
    fmt.Println("循环中使用 defer:", time.Since(start))
    
    // 优化:将 defer 移到循环外(若逻辑允许)
    start = time.Now()
    for i := 0; i < 1000000; i++ {
        // 无 defer
    }
    fmt.Println("无 defer:", time.Since(start))
}

func main() {
    deferPerformance()
    // 输出(示例):
    // 循环中使用 defer: 68.178292ms
    // 无 defer: 299.916µs
}

循环中使用 defer 会导致大量函数被压栈,建议仅在必要时使用,或通过其他方式优化(如将 defer 移到循环外)。

(9)其他例子参考
Go 复制代码
package main

import "fmt"

// 基础案例:展示defer与return的执行顺序
func basicCase() {
    fmt.Println("=== 基础案例 ===")
    
    defer func() {
        fmt.Println("defer 执行")
    }()
    
    fmt.Println("函数执行中")
    return
    // 输出顺序:
    // 函数执行中
    // defer 执行
}

// 案例1:带返回值的函数中defer的执行
func returnWithDefer() int {
    fmt.Println("=== 带返回值的函数 ===")
    
    defer func() {
        fmt.Println("defer 执行")
    }()
    
    fmt.Println("函数执行中")
    return 100
    // 输出顺序:
    // 函数执行中
    // defer 执行
}

// 案例2:defer修改具名返回值
func deferModifyReturn() (result int) {
    fmt.Println("=== defer修改具名返回值 ===")
    
    defer func() {
        result += 50 // 修改返回值
        fmt.Println("defer 执行,result变为", result)
    }()
    
    fmt.Println("函数执行中,准备返回50")
    return 50
    // 输出顺序:
    // 函数执行中,准备返回50
    // defer 执行,result变为 100
    // 实际返回值为100
}

// 案例3:多个defer的执行顺序(后进先出)
func multipleDefers() {
    fmt.Println("\n=== 多个defer的执行顺序 ===")
    
    defer fmt.Println("defer 1 执行")
    defer fmt.Println("defer 2 执行")
    defer fmt.Println("defer 3 执行")
    
    fmt.Println("函数执行中")
    // 输出顺序:
    // 函数执行中
    // defer 3 执行
    // defer 2 执行
    // defer 1 执行
}

// 案例4:defer函数参数的计算时机
func deferParamTiming() {
    fmt.Println("\n=== defer参数的计算时机 ===")
    
    x := 10
    // 参数在声明时计算
    defer fmt.Printf("defer 执行,x=%d\n", x)
    
    x = 20
    fmt.Printf("函数执行中,x=%d\n", x)
    // 输出顺序:
    // 函数执行中,x=20
    // defer 执行,x=10
}
// 案例4.1:defer函数参数的计算时机
func deferParamTiming1() {
    fmt.Println("\n=== 4.1 defer参数的计算时机 ===")
    
    x := 10
    //注意,这里跟上面有区别,此情况属于闭包捕获外部变量x,是引用传值
    defer func() {fmt.Printf("defer 执行,x=%d\n", x) }()
    
    x = 20
    fmt.Printf("函数执行中,x=%d\n", x)
    // 输出顺序:
    // 函数执行中,x=20
    // defer 执行,x=10
}

// 案例5:返回值是函数调用的
func modifiedOriginalExample() {
    fmt.Println("\n=== 返回值是函数调用的 ===")
    result := returnAndDefer()
    fmt.Printf("main函数中收到的返回值: %d\n", result)
}

func deferFunc() int {
    fmt.Println("deferFunc called")
    return 10 // 修改返回值,展示defer的返回值会被忽略
}

func returnFunc() int {
    fmt.Println("returnFunc called")
    return 20 // 修改返回值,方便观察
}

func returnAndDefer() int {
    defer deferFunc() // defer的返回值会被忽略
    return returnFunc()
    // 输出顺序:
    // returnFunc called
    // deferFunc called
    // 实际返回值为20
}

func calculate(a int, b int) int {
    fmt.Printf("计算: %d\n", a)
    return a
}

// 情况1:命名返回值被 defer 修改
func Example1(n int) (result int) {
    result = n * 2        // result = 10
    defer func() {
        result += 5       // 修改命名返回值:10 + 5 = 15
    }()
    return result         // 返回 15
}

// 情况2:非命名返回值,defer 修改不影响返回值
func Example2(n int) int {
    temp := n + 3         // temp = 8
    defer func() {
        temp *= 2         // temp = 16,但返回值已确定
    }()
    return temp           // 返回值在此时确定 = 8
}

// 情况3:defer 修改命名返回值(复杂计算)
func Example3(n int) (total int) {
    defer func() {
        total += n * 3    // total = 7 + 5*3 = 22
    }()
    total = n + 2         // total = 7
    return total          // 返回 22
}

// 情况4:defer 参数立即求值 vs 闭包捕获
func Example4() (value int) {
    value = 10
    defer func(x int) {
        fmt.Printf("参数x(立即求值): %d\n", x)      // 10
        fmt.Printf("闭包value(最新值): %d\n", value) // 30
    }(value)           // value 此时为 10
    
    value = 20
    return 30          // value = 30
}

// 情况5:多个 defer 修改同一个命名返回值
func Example5(n int) (sum int) {
    defer func() {
        sum += n        // sum = 6 + 3 = 9
    }()
    
    defer func() {
        sum *= 2        // sum = 3 * 2 = 6
    }()
    
    sum = n             // sum = 3
    return sum          // 返回 9
}

func main() {
    basicCase()
    // === 基础案例 ===
    // 函数执行中
    // defer 执行
  
    fmt.Printf("returnWithDefer返回值: %d\n", returnWithDefer())
    // === 带返回值的函数 ===
    // 函数执行中
    // defer 执行
    // returnWithDefer返回值: 100
  
    fmt.Printf("deferModifyReturn返回值: %d\n", deferModifyReturn())
    // === defer修改具名返回值 ===
    // 函数执行中,准备返回50
    // defer 执行,result变为 100
    // deferModifyReturn返回值: 100
  
    multipleDefers()
    // === 多个defer的执行顺序 ===
    // 函数执行中
    // defer 3 执行
    // defer 2 执行
    // defer 1 执行

  
    deferParamTiming()
    // === defer参数的计算时机 ===
    // 函数执行中,x=20
    // defer 执行,x=10
  
    deferParamTiming1()
    // === 4.1 defer参数的计算时机 ===
    // 函数执行中,x=20
    // defer 执行,x=20
  
    modifiedOriginalExample()
    // returnFunc called
    // deferFunc called
    // main函数中收到的返回值: 20

    fmt.Println("calculate开始")
    defer calculate(10, calculate(30, 0))
    defer calculate(20, calculate(40, 0))
    fmt.Println("calculate结束")
    // calculate开始
    // 计算: 30
    // 计算: 40
    // calculate结束
    // 计算: 20
    // 计算: 10
  
    fmt.Println("Example1(5):", Example1(5))     // 15
    fmt.Println("Example2(5):", Example2(5))     // 8
    fmt.Println("Example3(5):", Example3(5))     // 22
    fmt.Println("Example4():")
    Example4()                                   // 参数x: 10, 闭包value: 30
    // 参数x(立即求值): 10
    // 闭包value(最新值): 30
    fmt.Println("Example5(3):", Example5(3))     // 9
}
相关推荐
西岸行者12 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意12 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码12 天前
嵌入式学习路线
学习
毛小茛12 天前
计算机系统概论——校验码
学习
babe小鑫12 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms12 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下12 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。12 天前
2026.2.25监控学习
学习
im_AMBER12 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J12 天前
从“Hello World“ 开始 C++
c语言·c++·学习