在Go语言中,defer语句会在当前函数返回之前执行被延迟的函数或方法。defer语句在声明时会捕获当前变量的值或引用,具体行为取决于变量的类型:

  1. 值类型变量(如整数、浮点数、字符串等):

    • defer会在声明时复制变量的当前值。
    • 之后对变量的修改不会影响defer捕获的值。
  2. 引用类型变量(如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]

步骤解释:

  1. 初始化slice

    s := []int{1, 2}
    
    • s指向一个底层数组[1, 2],长度和容量均为2。
  2. 修改slice的元素

    s[0] = 3
    
    • 修改了底层数组的第一个元素,底层数组变为[3, 2]
    • s仍然指向同一个底层数组。
  3. defer捕获slice的副本

    defer fmt.Println(s)
    
    • defer捕获了当前slice的副本,包括指向底层数组的指针、长度和容量。
    • 此时,s指向的底层数组是[3, 2]
  4. append操作

    s = append(s, 4)
    
    • s的容量为2,长度为2,append会导致底层数组重新分配。
    • 新的底层数组创建,容量可能增加到4,内容为[3, 2, 4]
    • s现在指向新的底层数组。
  5. defer执行

    • defer fmt.Println(s)执行时,使用的是defer捕获的slice副本,指向旧的底层数组[3, 2]
    • 因此,输出为[3, 2]

总结:

  • defer在声明时捕获了slice的副本,包括指向底层数组的指针。
  • append操作可能导致底层数组重新分配,但defer捕获的副本仍然指向旧的底层数组。
  • 因此,defer fmt.Println(s)输出的是append之前的slice内容[3, 2]