在Go语言中,defer
语句会在当前函数返回之前执行被延迟的函数或方法。defer
语句在声明时会捕获当前变量的值或引用,具体行为取决于变量的类型:
-
值类型变量(如整数、浮点数、字符串等):
defer
会在声明时复制变量的当前值。- 之后对变量的修改不会影响
defer
捕获的值。
-
引用类型变量(如slice、map、指针等):
defer
会在声明时捕获变量的引用。- 之后对变量所指向的数据进行修改会影响
defer
的输出,因为defer
使用的是同一个引用。
示例1:整数值类型
package main
import "fmt"
func main() {
s := 1
defer fmt.Println(s) // 捕获s的值1
s = 3
fmt.Println("最终s的值:", s)
}
输出:
最终s的值: 3
1
解释:
defer fmt.Println(s)
在声明时捕获了s
的值1。- 之后
s
被赋值为3,但defer
已经保存了原来的值1,所以打印的是1。
示例2:slice引用类型
package main
import "fmt"
func main() {
s := []int{1, 2}
defer fmt.Println(s) // 捕获s的引用
s[0] = 3
fmt.Println("最终s的值:", s)
}
输出:
最终s的值: [3 2]
[3 2]
解释:
defer fmt.Println(s)
捕获了s
的引用,指向底层数组[1, 2]
。- 之后修改
s[0]
为3,改变了底层数组的内容。 defer
执行时,使用的是同一个引用,打印的是修改后的[3, 2]
。
总结:
- 对于值类型变量,
defer
捕获的是变量的值副本,修改变量不会影响defer
的输出。 - 对于引用类型变量,
defer
捕获的是变量的引用,修改底层数据会影响defer
的输出。
通过理解defer
对不同类型变量的捕获方式,可以更好地控制和预测程序的行为。
package main
import "fmt"
func main() {
s := []int{1, 2}
s[0] = 3
defer fmt.Println(s)
s = append(s, 4)
}
在这个Go代码中,s
是一个slice,初始值为[]int{1, 2}
。执行defer fmt.Println(s)
时,s
的值被捕获。当append(s, 4)
执行后,s
的底层数组发生了变化,但defer
捕获的是append
之前的slice
副本。因此,defer fmt.Println(s)
输出的是[3, 2]
。
步骤解释:
-
初始化
slice
:s := []int{1, 2}
s
指向一个底层数组[1, 2]
,长度和容量均为2。
-
修改
slice
的元素:s[0] = 3
- 修改了底层数组的第一个元素,底层数组变为
[3, 2]
。 s
仍然指向同一个底层数组。
- 修改了底层数组的第一个元素,底层数组变为
-
defer
捕获slice
的副本:defer fmt.Println(s)
defer
捕获了当前slice
的副本,包括指向底层数组的指针、长度和容量。- 此时,
s
指向的底层数组是[3, 2]
。
-
append
操作:s = append(s, 4)
s
的容量为2,长度为2,append
会导致底层数组重新分配。- 新的底层数组创建,容量可能增加到4,内容为
[3, 2, 4]
。 s
现在指向新的底层数组。
-
defer
执行:defer fmt.Println(s)
执行时,使用的是defer
捕获的slice
副本,指向旧的底层数组[3, 2]
。- 因此,输出为
[3, 2]
。
总结:
defer
在声明时捕获了slice
的副本,包括指向底层数组的指针。append
操作可能导致底层数组重新分配,但defer
捕获的副本仍然指向旧的底层数组。- 因此,
defer fmt.Println(s)
输出的是append
之前的slice
内容[3, 2]
。