开发者

golang中defer延迟机制的实现示例

开发者 https://www.devze.com 2025-09-29 10:51 出处:网络 作者: lmryBC49
目录defer是什么defer定义注意事项多个defer的执行顺序延迟参数传入时机基本语法小结声明时机和执行时机声明时机执行时机defer与return的区别函数返回值不具名返回具名返回defer是什么
目录
  • defer是什么
  • defer定义
    • 注意事项
  • 多个defer的执行顺序
    • 延迟参数传入时机
      • 基本语法
      • 小结
    • 声明时机和执行时机
      • 声明时机
      • 执行时机
    • defer与return的区别
      • 函数返回值
        • 不具名返回
        • 具名返回

    defer是什么

    defer是go中一种延迟调用机制。

    执行时机

    defer后面的函数只有在当前函数执行完毕后才能执行。

    执行顺序

    将延迟的语句按defer的逆序进行执行,也就是说先被defer的语句最后被执行,最后被defer的语句,最先被执行,通常用于释放资源。

    多个defer本质就是用栈存储,先进后出。

    defer定义

    //最后不要忘记函数调用
    //使用匿名函数
    defer func (形参列表){
        
    }(实参)
    
    
    //最后不要忘记调用函数
    //使用事先声明的函数
    defer 函数名(实参)
    
    //最后不要忘记调用方法
    defer 方法名(形参列表){
        
    }(实参)
    

    defer的功能一般是用于释放资源。

    defer后面的函数是可以有返回值的,但是一般没有作用。

    注意事项

    方法或函数必需调用

    //报错:未调用函数
    defer func(){
        fmt.Println("a")
    }
    

    注意声明顺序

    虽然defer的执行时机在函数结束后,但是声明的时候使用的变量或者参数得是函数内在defer声明之前就定义好的。

    //报错:student未定义
    defer student.GetName(2)
    var student Student
    
    //报错:age未定义
    defer func(a int){
        fmt.Printf("年龄为%d\n",age)
    }(age)
    age := 15
    

    多个defer的执行顺序

    多个defer出现的时候,它会把defer之后的函数压入一个栈中延迟执行,也就是先进后出(LIFO).

    写在前面的defer会比写在后面的defer调用的晚。下面通过一个示例看一下:

    package defer_knowledge
    
    import "fmt"
    
    type Student struct{
    	Name string
    }
    
    func (s Student) GetName(n int){
    	fmt.Printf("这是第%d个defer\n",n)
    }
    
    func sayHello(n int){
    	fmt.Printf("这是第%d个defer\n",n)
    }
    
    //验证defer的执行顺序
    func DeferFirst(){
    	fmt.Println("hello world")
    	var age int = 25
    
    	defer func (){
    		fmt.Println("我是第1个defer")
    	}()
    
    	age++
    	
    
    	/*
    		虽然defer的执行时机在return之后
    		但是声明defer时,结构体实例要先声明,否则无法访问结构体实例方法
    	*/
    	var student Student
    	defer student.GetName(2)
    	
    	defer func (){
    		fmt.Println("我是第3个defer")
    	}()
    
    	defer sayHello(4)
    }
    

    结果

    这是第4个defer

    我是第3个defer

    这是第2个defer

    我是第1个defer

    图示

    golang中defer延迟机制的实现示例

    延迟参数传入时机

    基本语法

    注意事项

    defer函数的入参参数是在defer函数声明时决定的。例如

    package defer_knowledge
    
    import "fmt"
    
    //defer的参数是声明时传入的
    func DeferParams(){
    	var age = 10
    	defer func(a int){
    		fmt.Printf("defer内的参数为%d\n",a)
    	}(age)
    
    	编程客栈age = 25
    
    	fmt.Printf("age已经变成了%d\n",age)
    }
    

    调用结果

    age已经变成了25
    defer内的参数为10
    

    小结

    值类型

    所以我们要注意传入的参数,

    【值类型参数】

    值类型参数原始变量改变不影响传入参数,例如int、数组、结构体

    如果我们想要defer执行时能读取到变化后的"值类型"参数,可以传入指针

    例如

    package defer_knowledge
    import "fmt"
    //defer的参数是声明时传入的
    func DeferParams(){
    	var age = 10
    	//如果想要追踪值类型的变化可以传入值类型指针
    	defer func(a *int){
    		fmt.Printf("最初如果传入指针,defer内参数为%d\n",*a)
    	}(&age)
    
    	defer func(a int){
    		fmt.Printf("defer内的参数为%d\n",a)
    	}(age)
    
    	age = 25
    
    	fmt.Printf("age已经变成了%d\n",age)
    }
    

    调用结果

    age已经变成了25

    defer内的参数为10

    最初如果传入指针,defer内参数为25

    引用类型

    具体看引用的底层是否发生变换,例如切片,如果没发生扩容将使用相同的。

    package defer_knowledge
    import "fmt"
    func DeferParams2(){
    	var arr = make([]int,5,5)
    	//引用类型直接传递即可,将追踪到引用改变为止
    	defer func(a []int){
    		fmt.Printf("defer内的参数为%#v\n",a)
    	}(arr)
    
    	arr[2] = 10
    	fmt.Printf("arr已经变成了%#v\n",arr)
    }
    

    调用结果

    arr已经变成了[]int{0, 0, 10, 0, 0}

    defer内的参数为[]int{0, 0, 10, 0, 0}

    声明时机和执行时机

    声明时机

    defer的声明时机时按照他出现在代码中的顺序,这时会执行两个操作。

    1.传入参数

    2.检查内部要访问的变量是否已经定义

    举例

    func DeferTime(){
        var age int
        /*
        	声明时会传入参数,以及检查内部逻辑是否正确
        */
        defer func(){
            //注意,这个不是defer函数的参数
            //和常规变量作用域一样,本层找不到就去外面找
            age++
        }()
    }
    

    错误示范

    func DeferTime(){
        defer func(){
            //报错:age未定义
            age++
        }()
        var age int
    }
    

    执行时机

    defer的执行时机是在函数逻辑结束后,或者说return后,按照defer栈调用。

    举例

    func DeferTime2(){
    	var age int 
    
    	defer func(){
    		//按照defer栈,此时访问到的age为 11
    		age = age+5
    		fmt.Printf("age的值为%d\n",age)
    	}()
    
    	defer func(){
    		//defer执行时机为函数结束后,所以此时访问到的 age = 10
    		age++
    		fmt.Printf("age的值为%d\n",age)
    	}()
    
    	age = 10
    }
    

    结果

    age的值为11

    age的值为16

    defer与return的区别

    图示

    golang中defer延迟机制的实现示例

    可以看到 return 执行的时候,并不是原子性操作,一般是分为两步:将结果x赋值给了返回值,然后执行了RET指令;而defer语句执行的时候,是在赋值变量之后,在RET指令之前。所以这里注意一下。返回值和x的关系。如果x是一个值类型,这里是进行了拷贝的。

    执行图示意

    golang中defer延迟机制的实现示例

    函数返回值

    不具名返回

    形式

    func 函数名(参数列表) 返回值类型{
        return 返回值
    }
    
    //例如
    /*
    	return sum
    	操作拆解:
    	实际对外暴露返回值为 sum_copy
    	sum_copy = sum
    
    	所以return实际执行拷贝操作,他不是将函数内的变量抛出,而是将拷贝后的值抛出
    */
    func Add(a,b int) int{
        sum := a+b
        return sum
    }
    

    案例1

    func DeferAndReturn1() int{
        var num int
        defer func(){
            num++
            //num的值为16
            fmt.Printf("num的值为%d\n",num)
        }()
        num = 15
        return num
    }
    
    //调用
    target := DeferAndReturn1()
    //target的值为15
    fmt.Printf("target的值为%d\n",target)
    

    解析

    func DeferAndReturn1() int{
        var num int
        defer func(){
            num++
            //num的值为16
            fmt.Printf("num的值为%d\n",num)
        }()
        num = 15
        /*
        	实际操作
        	copy_num = num
        	对外暴露copy_num,
        	由于num是值类型,所以后续defer中对num的操作不影响copy_num
        */
        return num
    }
    

    误区1

    想到了指针操作,但是理解出错。

    func DeferAndReturn1() int{
        var num int
        var ptr = new(int)
        ptr = &num
        defer func(){
            *ptr++
            //num的值为16
            fmt.Printf("num的值为%d\n",num)
        }()
        num = 15
        return num
    }
    //调用
    target := DeferAndReturn1()
    //target的值为15
    fmt.Printf("target的值为%djavascript\n",target)
    

    原因:

    func DeferAndReturn1() int{
        var num int
        var ptr = new(int)
        ptr = &num
        defer func(){
            *ptr++
            //num的值为16
            fmt.Printf("num的值为%d\n",num)
        }
        num = 15
        /*
        	实际操作
        	copy_num = num
        	对外暴露copy_num,
        	我们修改通过指针ptr修改nupythonm的值,还是没影响到copy_num
        */
        return num
    }
    

    正确思维

    func DeferAndReturn1() *int{
        var num int
        var ptr = new(int)
        ptr = &num
        defer func(){
            num++
            //num的值为16
            fmt.Printf("num的值为%d\n",num)
        }()
        num = 15
        return ptr
    }
    //调用
    target := DeferAndReturn1()
    //target的值为16
    fmt.Printf("target的值为%d\n",*target)
    

    结果

    num的值为16

    target的值为16

    原因

    func DeferAndReturn1() *int{
        var num int
        var ptr = new(int)
        ptr = &num
        defer func(){
            num++
            //num的值为16
            fmt.Printf("num的值为%d\n",num)
        }()
        num = 15
        /*
        	实际操作
        	copy_ptr = ptr
        	由于ptr是引用类型,所以defer对ptr的影响会影响到cojavascriptpy_ptr
        	
        	num的本质就是*ptr,操作num就是在操作ptr
        */
        return ptr
    }
    

    具名返回

    相当于实际要暴露的返回值早就确定好了,return只是起到一个结束函数的作用。

    func 函数名(参数列表)(返回值 返回值类型){
        return
    }
    
    //示例
    /*
    	sum就是实际暴露的返回值,且已经声明了
    */
    func Add(a,b int) (sum int){
        return
    }
    

    案例1

    func DeferAndReturn2() (num int){
    	defer func(){
    		num++
    	}()
    	num = 10
    	return
    }
    
    num2 := DeferAndReturn2()
    fmt.Printf("外部num的值为%d\n",num2)
    

    结果

    外部num的值为11

    原因

    func DeferAndReturn2() (num int){
    	defer func(){
    		num++
    	}()
    	num = 10
        /*
        	这里写return 和 return num一样
        	最终暴露的值为 num
        	所以defer中对num的操作会影响到最终返回值
        */
    	return
    }
    

    到此这篇关于golang中编程客栈defer延迟机制的实现示例的文章就介绍到这了,更多相关golang defer延迟机制内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)! 

    0

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    关注公众号