一、map作为参数
makemap
:返回的是一个 *hmap
(指向hmap结构体的指针)。
- Map变量本身就是一个指针。
makeslice
:返回的是一个 slice
结构体(包含array
指针、len
、cap
三个字段)。
- 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分离 |
这种设计差异是有其合理性的:
-
Map的指针设计:Map的实现非常复杂,涉及哈希桶、扩容等机制。通过返回指针,可以确保所有的map操作都作用于同一个map实体,避免复制整个map结构的开销。
-
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)defer
与 panic
:在 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)defer
与 recover
:捕获 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
中生效,若没有 defer
,panic
会一直向上传播直至程序退出。
(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
}