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
}
相关推荐
渡我白衣3 小时前
深度学习入门(一)——从神经元到损失函数,一步步理解前向传播(上)
人工智能·深度学习·学习
敲代码的嘎仔3 小时前
JavaWeb零基础学习Day2——JS & Vue
java·开发语言·前端·javascript·数据结构·学习·算法
Amy_au4 小时前
AWS Lambda 学习笔
学习·云计算·aws
chennn124 小时前
c++相关学习
开发语言·c++·学习
Gorgous—l6 小时前
数据结构算法学习:LeetCode热题100-矩阵篇(矩阵置零、螺旋矩阵、旋转图像、搜索二维矩阵 II)
数据结构·学习·算法
eggcode6 小时前
Vue前端开发学习的简单记录
vue.js·学习
你也渴望鸡哥的力量么6 小时前
爬虫学习笔记
笔记·爬虫·学习
日更嵌入式的打工仔6 小时前
InitLWIP() 初始化
笔记·嵌入式硬件·学习
QAQ小菜鸟6 小时前
AutoCAD如何将指定窗口导出成PDF?
学习