Golang中的defer

面试常问之defer()的执行次序


情形1


package main

func main() {

    defer print(123)
 defer_call()
 defer print(789//panic之后的代码不会被执行
 print("不会执行到这里")
}

func defer_call() {
 defer func() {
  print("打印前")
 }()
 defer func() {
  print("打印中")
 }()

 defer print("打印后")

 panic("触发异常")

    defer print(666)   //IDE会有提示: Unreachable code
    
}

结果为:

打印后打印中打印前123panic: 触发异常

goroutine 1 [running]:
main.defer_call()
 /Users/shuangcui/explore/panicandrecover.go:19 +0xe5
main.main()
 /Users/shuangcui/explore/panicandrecover.go:6 +0x51

可见:

  • panic之后的defer()不会被执行
  • panic之前的defer(),按照 先进后出的次序执行,最后输出panic信息

(defer机制底层,是用链表实现的一个栈)

再如:

func main() {

 fmt.Println(123)

 defer fmt.Println(999)

 subfunc()

}

func subfunc() {

 defer fmt.Println(888)

 for i := 0; i > 10; i++ {
  fmt.Println("当前i为:", i)
  panic("have a bug")
 }

 defer fmt.Println(456)

}

结果为:

123
456
888
999

defer会延迟到当前函数执行 return 命令前被执行, 多个defer之间按LIFO先进后出顺序执行




情形2 (在defer内打印defer之外的主方法里操作的变量)


package main

import "fmt"

func main() {
 foo()
}

func foo() {
 i := 0
 defer func() {
  //i--
  fmt.Println("第一个defer", i)
 }()

 i++
 fmt.Println("+1后的i:", i)

 defer func() {
  //i--
  fmt.Println("第二个defer", i)
 }()

 i++
 fmt.Println("再+1后的i:", i)

 defer func() {
  //i--
  fmt.Println("第三个defer", i)
 }()

 i++
 fmt.Println("再再+1后的i:", i)

 i = i + 666

 fmt.Println("+666后的i为:", i)

}

输出为:

+1后的i: 1
再+1后的i: 2
再再+1后的i: 3
+666后的i为: 669
第三个defer 669
第二个defer 669
第一个defer 669


情形3 (在defer内外操作同一变量)


package main

import "fmt"

func main() {
 foo()
}

func foo() {
 i := 0
 defer func() {
  i--
  fmt.Println("第一个defer", i)
 }()

 i++
 fmt.Println("+1后的i:", i)

 defer func() {
  i--
  fmt.Println("第二个defer", i)
 }()

 i++
 fmt.Println("再+1后的i:", i)

 defer func() {
  i--
  fmt.Println("第三个defer", i)
 }()

 i++
 fmt.Println("再再+1后的i:", i)

 i = i + 666

 fmt.Println("+666后的i为:", i)

}

输出为:

+1后的i: 1
再+1后的i: 2
再再+1后的i: 3
+666后的i为: 669
第三个defer 668
第二个defer 667
第一个defer 666

情形4! (发生了参数传递!---传递参数给defer后面的函数, defer内外同时操作该参数)


package main

import "fmt"

func main() {
 foo2()
}

func foo2() {
 i := 0
 defer func(k int) {
  //k--
  fmt.Println("第一个defer", k)
 }(i)

 i++
 fmt.Println("+1后的i:", i)

 defer func(k int) {
  //k--
  fmt.Println("第二个defer", k)
 }(i)

 i++
 fmt.Println("再+1后的i:", i)

 defer func(k int) {
  //k--
  fmt.Println("第三个defer", k)
 }(i)

 i++
 fmt.Println("再再+1后的i:", i)

 i = i + 666

 fmt.Println("+666后的i为:", i)

}

输出为:

+1后的i: 1
再+1后的i: 2
再再+1后的i: 3
+666后的i为: 669
第三个defer 2
第二个defer 1
第一个defer 0

如果取消三处k--的注释, 输出为:

+1后的i: 1
再+1后的i: 2
再再+1后的i: 3
+666后的i为: 669
第三个defer 1
第二个defer 0
第一个defer -1

等同于:

package main

import "fmt"

func main() {
 foo3()
}

func foo3() {
 i := 0
 defer f1(i)

 i++
 fmt.Println("+1后的i:", i)


 defer f2(i)

 i++
 fmt.Println("再+1后的i:", i)

 defer f3(i)
 i++
 fmt.Println("再再+1后的i:", i)

 i = i + 666

 fmt.Println("+666后的i为:", i)

}

func f1(k int) {
 k--
 fmt.Println("第一个defer", k)
}

func f2(k int) {
 k--
 fmt.Println("第二个defer", k)
}

func f3(k int) {
 k--
 fmt.Println("第三个defer", k)
}

defer指定的函数的参数在 defer 时确定,更深层次的原因是Go语言都是值传递。


情形5! (传递指针参数!---传递参数给defer后面的函数, defer内外同时操作该参数)


package main

import "fmt"

func main() {

 foo5()
}

func foo5() {
 i := 0
 defer func(k *int) {
  fmt.Println("第一个defer", *k)
 }(&i)

 i++
 fmt.Println("+1后的i:", i)

 defer func(k *int) {
  fmt.Println("第二个defer", *k)
 }(&i)

 i++
 fmt.Println("再+1后的i:", i)

 defer func(k *int) {
  fmt.Println("第三个defer", *k)
 }(&i)

 i++
 fmt.Println("再再+1后的i:", i)

 i = i + 666

 fmt.Println("+666后的i为:", i)
}

输出为:

+1后的i: 1
再+1后的i: 2
再再+1后的i: 3
+666后的i为: 669
第三个defer 669
第二个defer 669
第一个defer 669

作如下修改:

package main

import "fmt"

func main() {

 foo5()
}

func foo5() {
 i := 0
 defer func(k *int) {
  (*k)--
  fmt.Println("第一个defer", *k)
 }(&i)

 i++
 fmt.Println("+1后的i:", i)

 defer func(k *int) {
  (*k)--
  fmt.Println("第二个defer", *k)
 }(&i)

 i++
 fmt.Println("再+1后的i:", i)

 defer func(k *int) {
  (*k)--
  fmt.Println("第三个defer", *k)
 }(&i)

 i++
 fmt.Println("再再+1后的i:", i)

 i = i + 666

 fmt.Println("+666后的i为:", i)
}

输出为:

+1后的i: 1
再+1后的i: 2
再再+1后的i: 3
+666后的i为: 669
第三个defer 668
第二个defer 667
第一个defer 666

总结一下


  • 如果传参进defer后面的函数(无论是闭包func(){}(i)方式还是子方法f(i)方式,或是直接跟如fmt.Println(i)),defer回溯时均以当时传参时i的值去计算

  • 反之,defer回溯时,以最后i的值带入计算;(参考下面的例子).

参考:

Go面试题答案与解析[1]




几种写法之间的归类与区别


package main

import "fmt"

func main() {
 rs := foo6()
 fmt.Println("in main func:", rs)
}

func foo6() int {
 i := 0
 defer fmt.Println("in defer :", i)

 //defer func() {
 // fmt.Println("in defer :", i)
 //}()

 i = 1000
 fmt.Println("in foo:", i)
 return i+24
}

输出为:

in foo: 1000
in defer : 0
in main func: 1024

如果改为:

package main

import "fmt"

func main() {
 rs := foo6()
 fmt.Println("in main func:", rs)
}

func foo6() int {
 i := 0
 //defer fmt.Println("in defer :", i)
 defer func() {
  fmt.Println("in defer :", i)
 }()

 i = 1000
 fmt.Println("in foo:", i)
 return i+24
}

输出为:

in foo: 1000
in defer : 1000
in main func: 1024

也可见,

defer fmt.Println("in defer :", i)

相当于

defer func(k int) {
  fmt.Println(k)
    }(i)

func f(k int){
 fmt.Println(k)
}

这时的参数,都是传递时的值

而如

 defer func() {
  fmt.Println("in defer :", i)
    }()

这时的参数,为最后return之前那一刻的值




defer会影响返回值吗?


函数的return value 不是原子操作, 在编译器中实际会被分解为两部分:返回值赋值return 。而defer刚好被插入到末尾的return前执行(即defer介于二者之间)。故可以在defer函数中修改返回值

package main

import (
 "fmt"
)

func main() {
 fmt.Println(doubleScore(0))    //0
 fmt.Println(doubleScore(20.0)) //40
 fmt.Println(doubleScore(50.0)) //50
}
func doubleScore(source float32) (rs float32) {
 defer func() {
  if rs < 1 || rs >= 100 {
   //将影响返回值
   rs = source
  }
 }()
 rs = source * 2
 return
 //或者
 //return source * 2
}

输出为:

0
40
50

再如:

func main() {

    fmt.Println("foo return :", foo2())

}

func foo() map[string]string {

    m := map[string]string{}

    defer func() {
        m["a"] = "b"
    }()

    return m
}

输出为:

foo return : map[a:b]

又如:

package main


import "fmt"

func main() {
 fmt.Println("foo return :", foo())
}

func foo() int {
 i := 0

 defer func() {
  i = 10086
 }()

 return i + 5
}

输出为:

foo return : 5

若作如下修改:


func foo() (i int) {
 i = 0
 defer func() {
  i = 10086
 }()

 return i + 5
}

则返回为:

foo return : 10086

return之后的语句先执行,defer后的语句后执行


return value拆解为两步: 确定value值,然后return..即如果return 后面是个方法或者复杂表达式,且有某个值i,会先计算.完成后defer再执行,如果defer里面也有对i的改动,是可以影响返回值的

(给函数返回值申明变量名, 这时, 变量的内存空间空间是在函数执行前就开辟出来的,且该变量的作用域为整个函数,return时只是返回这个变量的内存空间的内容,因此defer能够改变返回值)

defer不影响返回值,除非是map、slice和chan这三种引用类型,或者返回值定义了变量名




参考:

Golang研学:如何掌握并用好defer[2]--存疑("引用传递"那里明显错误)

Golang中的Defer必掌握的7知识点


参考资料

[1]

Go面试题答案与解析: https://yushuangqi.com/blog/2017/golang-mian-shi-ti-da-an-yujie-xi.html

[2]

Golang研学:如何掌握并用好defer: https://segmentfault.com/a/1190000019063371#comment-area

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/30561.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Opencv-C++笔记 (14) : 霍夫变换(直线、圆)

文章目录 一、霍夫变换-直线1.1霍夫变换-直线 原理详解 二、霍夫圆检测 一、霍夫变换-直线 Hough Line Transform用来做直线检测 前提条件 – 边缘检测已经完成 1、平面空间&#xff08;x,y&#xff09;到极坐标空间转换&#xff1b; 2、对极坐标进行变换&#xff0c;转化为…

[mongo]应用场景及选型

应用场景及选型 MongoDB 数据库定位 OLTP 数据库横向扩展能力&#xff0c;数据量或并发量增加时候架构可以自动扩展灵活模型&#xff0c;适合迭代开发&#xff0c;数据模型多变场景JSON 数据结构&#xff0c;适合微服务/REST API基于功能选择 MongoDB 关系型数据库迁移 从基…

【Android】MVC,MVP,MVVM三种架构模式的区别

MVC 传统的代码架构模式&#xff0c;仅仅是对代码进行了分层&#xff0c;其中的C代表Controller&#xff0c;控制的意思 将代码划分为数据层&#xff0c;视图层&#xff0c;控制层&#xff0c;三层之间可以任意交互 MVP MVP是在MVC基础上改进而来的一种架构&#xff0c;其中的…

【图像去噪】基于原始对偶算法优化的TV-L1模型进行图像去噪研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

C++入门--string类的实现

目录 1.string类常用函数实现&#xff08;1&#xff09;string类成员变量定义&#xff08;2&#xff09; string类默认构造函数实现&#xff08;3&#xff09; string类拷贝构造函数实现&#xff08;4&#xff09;string类析构函数&#xff08;5&#xff09;string类c_str()函数…

品牌宣传与媒体传播是声誉管理的主要方式之一

企业声誉是现如今影响品牌信任度、客户忠诚度的重要因素&#xff0c;也被视为企业的一种无形资&#xff0c;更影响着企业未来的发展。因此&#xff0c;企业声誉管理也日渐成为企业管理的重要课题之一&#xff0c;尤其在品牌营销管理领域。 什么是声誉管理&#xff1f;声誉管理有…

举例说明typescript的Exclude、Omit、Pick

一、提前知识说明&#xff1a;联合类型 typescript的联合类型是一种用于表示一个值可以是多种类型中的一种的类型。我们使用竖线&#xff08;|&#xff09;来分隔每个类型&#xff0c;所以number | string | boolean是一个可以是number&#xff0c;string或boolean的值的类型。…

logstash 采集 docker 日志

1、nginx容器部署 参考&#xff1a;nginx容器部署 将容器内的nginx日志文件映射到宿主机/home/logs/nginx目录下 注意&#xff1a;并且需要需要将日志的输出格式修改为json 2、编辑vim /opt/logstash-7.4.2/config/nginx-log-es.conf 文件&#xff0c;收集docker nginx容器日…

Java训练五

一、跳动的心脏 心脏是动物的重要器官&#xff0c;不断跳动的心脏意味着鲜活的生命力。现在创建一个人类&#xff0c;把心脏类设计为人类里面的一个成员内部类。心脏类有一个跳动的方法&#xff0c;在一个人被创建时&#xff0c;心脏就开始不断地跳动。 package haha; publi…

Spring集成Seata

Seata的集成方式有&#xff1a; 1. Seata-All 2. Seata-Spring-Boot-Starter 3. Spring-Cloud-Starter-Seata 本案例使用Seata-All演示&#xff1a; 第一步&#xff1a;下载Seata 第二步&#xff1a;为了更好看到效果&#xff0c;我们将Seata的数据存储改为db 将seata\sc…

symfony3.4中根据角色不同跳转不同页面

在Symfony 3.4中&#xff0c;可以使用安全组件来实现控制不同角色跳转到不同页面的功能。 首先&#xff0c;确保你已经安装了Symfony的安全组件&#xff0c;并配置了安全相关的配置文件。这些文件通常是 security.yml 和 security.yml。 在配置文件中&#xff0c;你可以定义不…

Vue3 —— to 全家桶及源码学习

该文章是在学习 小满vue3 课程的随堂记录示例均采用 <script setup>&#xff0c;且包含 typescript 的基础用法 前言 本篇主要学习几个 api 及相关源码&#xff1a; toReftoRefstoRaw 一、toRef toRef(reactiveObj, key) 接收两个参数&#xff0c;第一个是 响应式对象…

目标检测中遇到的问题和 docker导出日志

一 docker容器导出日志 导出日志在Linux服务器的本地目录下&#xff0c;可以直接下载 docker logs 容器名称 > log.txt 二 Flask使用main执行 1 改dockerfile 文件内容 #CMD [ "python3", "-m" , "flask", "run", "--hos…

使用阿里云微调chatglm2

完整的代码可以参考&#xff1a;https://files.cnblogs.com/files/lijiale/chatglm2-6b.zip?t1691571940&downloadtrue # %% [markdown] # # 微调前# %% model_path "/mnt/workspace/ChatGLM2-6B/chatglm2-6b"from transformers import AutoTokenizer, AutoMo…

Elasticsearch 性能调优指南

目录 1、通用优化策略 1.1 通用最小化法则 1.2 职责单一原则 1.3 其他 2、写性能调优 2.1 基本原则 2.2 优化手段 2.2.1 增加 flush 时间间隔&#xff0c; 2.2.2 增加refresh_interval的参数值 2.2.3 增加Buffer大小&#xff0c; 2.2.4 关闭副本 2.2.5 禁用swap 2…

嘉楠勘智k230开发板上手记录(四)--HHB神经网络模型部署工具

按照K230_AI实战_HHB神经网络模型部署工具.md&#xff0c;HHB文档&#xff0c;RISC-V 编译器和模拟器安装来 一、环境 1. 拉取docker 镜像然后创建docker容器并进入容器 docker pull hhb4tools/hhb:2.4.5 docker run -itd --namehhb2_4 -p 22 "hhb4tools/hhb:2.4.5"…

【CSS】背景图定位问题适配不同机型

需求 如图, 实现一个带有飘带的渐变背景 其中头像必须显示飘带凹下去那里 , 需要适配不同的机型, 一不下心容易错位 实现 因为飘带背景是版本迭代中更新的, 所以飘带和渐变背景实则两个div 飘带切图如下 , 圆形部分需要契合头像 <view class"box-bg"><…

Linux ——实操篇

Linux ——实操篇 前言vi 和 vim 的基本介绍vi和vim常用的三种模式正常模式插入模式命令行模式 vi和vim基本使用各种模式的相互切换vi和vim快捷键关机&重启命令基本介绍注意细节 用户登录和注销基本介绍使用细节 用户管理基本介绍添加用户基本语法应用案例细节说明 指定/修…

Java基础(九)数组工具类

数组工具类 1. Arrays类 a. 导入方法 import java.util.Arrays;b. Arrays类常用的方法 方法返回类型说明equals(array1, array2)boolean比较两个数组是否相等sort(array)void对数组 array 的元素进行排序toString(array)String把一个数组 array 转换成一个字符串fill(array,…

获取接口的所有实现

一、获取接口所有实现类 方法1&#xff1a;JDK自带的ServiceLoader实现 ServiceLoader是JDK自带的一个类加载器&#xff0c;位于java.util包当中&#xff0c;作为 A simple service-provider loading facility。 &#xff08;1&#xff09;创建接口 package com.example.dem…