当前位置: 首页 > news >正文

Golang中defer和return的执行顺序 + 相关测试题(面试常考)

参考文章:

  • 【Golang】defer陷阱和执行原理
  • GO语言defer和return 的执行顺序
  • 深入理解Golang defer机制,直通面试

面试富途的时候,遇到了1.2的这个进阶问题,没回答出来。这种题简直是 噩梦\color{purple}{噩梦}噩梦,很久不关注基本上就忘记了…


一、defer相关测试题

1.1 关于 defer 函数后的 匿名/有名 返回值对输出结果的影响:

package mainimport ("fmt"
)func f1() int{var i intdefer func(){i++}()return i    
}func f2() (i int){defer func(){i++}()return i    
}func main() {fmt.Println(f1())fmt.Println(f2())
}

上面的程序输出结果是啥呢?答案是:0 1

我们知道Golang中的函数返回值有匿名和有名两种

  • 对于匿名的可以理解成执行return 语句时,分成两步,第一步需要设置一个临时变量s用来接收返回值,第二步将临时变量s返回。
  • 对于有名的可以理解成执行return的时候,直接将变量返回。

而我们知道,所有的defer都将在真正的 return 变量之前运行,所以对于上面两种情况,defer对于返回值的影响也有两种:

  • 对于匿名的:第一步设置临时变量保存返回值;第二步按照defer的执行步骤执行defer语句,如果其中有对变量的修改,将不会影响s变量的值。
  • 对于有名的:第一步先执行defer,对变量进行修改;第二步,返回被修改的返回值。

所以,理解了上面的步骤的之后,我们就可以理解 f1()f2() 这两个的函数了,在执行 return 时:

  • f1函数先通过临时变量s保存i的值,此时i为0,所以s=0,然后执行defer,修改i的值,i变为1,最后返回s的值,因为s=0,所以返回值是0;
  • 而f2是有名函数,所以执行return 的时候,i被设置为1,然后真正的返回i值,所以返回的是1。

1.2 关于defer的进阶测试题

再来看一段示例代码:

可以先尝试想想以下的几个函数分别会输出什么内容?

func test1() (x int) {defer fmt.Printf("in defer: x = %d\n", x)x = 7return 9
}func test2() (x int) {x = 7defer fmt.Printf("in defer: x = %d\n", x)return 9
}func test3() (x int) {defer func() {fmt.Printf("in defer: x = %d\n", x)}()x = 7return 9
}func test4() (x int) {defer func(n int) {fmt.Printf("in defer x as parameter: x = %d\n", n)fmt.Printf("in defer x after return: x = %d\n", x)}(x)x = 7return 9
}func main() {fmt.Println("test1")fmt.Printf("in main: x = %d\n", test1())fmt.Println("test2")fmt.Printf("in main: x = %d\n", test2())fmt.Println("test3")fmt.Printf("in main: x = %d\n", test3())fmt.Println("test4")fmt.Printf("in main: x = %d\n", test4())
}

你已经计算出结果了吗?看看和运行结果是不是一样的,如果不一样继续阅读本文吧:

test1
in defer: x = 0
in main: x = 9
test2
in defer: x = 7
in main: x = 9
test3
in defer: x = 9
in main: x = 9
test4
in defer x as parameter: x = 0
in defer x after return: x = 9
in main: x = 9

进阶测试题 解析

在看解析之前,先理解下 return 语句的执行顺序:
return语句本身并不是一条原子指令,它会先给返回值赋值,然后再是真正的返回结果到函数外部,如下:

func f() (r int) {return 1
}//执行过程:
r:=1 //赋值
ret  //执行返回

而在含defer表达式时,函数返回的过程是这样的:
先给返回值赋值,然后调用defer表达式,最后再是返回结果

这4个测试函数中,都是return 9并且没有对返回值进行修改,所以主函数main()中的输出都是:in main: x = 9,我相信这个大家应该是没有疑问的。接下来看每个测试函数defer的打印。

test1:defer执行时,对Printf的入参x进行计算,它的值是0,并且传递给函数,return 9后执行Printf,所以结果是in defer: x = 0。

func test1() (x int) {// defer后接【表达式】:// 起初x作为入参值为0,结果一直保留在栈中,最后直接打印输出,不受后续影响defer fmt.Printf("in defer: x = %d\n", x)x = 7// 返回值为 9return 9
}

test2:与test1类似,不同的是,defer执行是在x=7之后,所以x的值是7,并且传递给Printf,所以结果是:in defer: x = 7。

func test2() (x int) {x = 7// defer后接【表达式】:// 起初x作为入参值为7,结果一直保留在栈中,最后直接打印输出,不受后续影响defer fmt.Printf("in defer: x = %d\n", x)// 返回值为 9return 9
}

test3defer后跟的是一个匿名函数,匿名函数能访问外部函数的变量,这里访问的是test3的x,defer执行时,匿名函数没有入参,所以把func()()压入到栈,return语句之后,执行func()(),此时匿名函数获得x的值是9,所以结果是in defer: x = 9。

func test3() (x int) {// defer后接【匿名函数】:defer func() {// 匿名函数能访问外部函数的变量,受后续return x的结果影响// 也就是说【defer匿名函数内要访问的变量 x】最终会被【defer匿名函数外最终要 return 返回出去的变量 x】所影响fmt.Printf("in defer: x = %d\n", x)}()x = 7// 返回值为 9return 9
}

test4:与test3的不同是,匿名函数有一个入参n,我们把x作为入参打印,还有就是匿名函数中的x能访问外部函数的变量x。defer执行时,x=0,所以入栈的函数是func(int)(0),return语句之后执行func(int)(0),即n=0,x在匿名函数内没有定义,依然访问test4中的x,此时x=9,所以结果为:in defer x as parameter: x = 0, in defer x after return: x = 9。

func test4() (x int) {// defer后接【匿名函数】:defer func(n int) {// 针对【defer匿名函数内要访问的变量 n】,其值取决于在一开始遇到 defer 时入参 n 的值,// 也就是最开始还没被改变时的变量 x 的值,将其赋值给入参 n,起初是0// 这个变量 n 的值是独立的,不会受后续 return x 结果的影响。fmt.Printf("in defer x as parameter: x = %d\n", n)fmt.Printf("in defer x after return: x = %d\n", x)}(x)x = 7return 9
}

小结:

  • 首先要明确 defer 后紧接的代码可以有两种写法:
    • defer + 表达式,例如:defer fmt.Printf("in defer: x = %d\n", x)
      此时就会直接保留当前变量 x 已有的值,一直到最后直接打印输出,不再受后续 return x 结果的影响。例如test1和test2中的变量x。\color{red}{例如 test1 和 test2 中的变量 x。}例如test1test2中的变量x
    • defer + 匿名函数(无入参/有入参),例如:test3中的 defer func() {fmt.Printf("in defer: x = %d\n", x)}()或 test4中 defer func(n int) {fmt.Printf("in defer x as parameter: x = %d\n", n) fmt.Printf("in defer x after return: x = %d\n", x)}(x)
      此时需要区分打印输出的变量到底是【defer匿名函数内要访问的变量 n】,还是【defer匿名函数内要访问的变量 x】。
      • 针对【defer匿名函数内要访问的变量 n】,其值取决于在一开始遇到 defer 时入参 n 的值(也就是最开始还没被改变时的变量 x 的值,起初是0)。这个变量 n 的值是独立的,不会受后续 return x 结果的影响。例如test4中的变量n。\color{red}{例如 test4 中的变量 n。}例如test4中的变量n
      • 针对【defer匿名函数内要访问的变量 x】,由于匿名函数能访问外部函数的变量,也就是说【defer匿名函数内要访问的变量 x】最终会被【defer匿名函数外最终要 return 返回出去的变量 x】所影响。例如test3和test4中的变量x。\color{red}{例如 test3 和 test4 中的变量 x。}例如test3test4中的变量x

相关文章:

Golang中defer和return的执行顺序 + 相关测试题(面试常考)

参考文章: 【Golang】defer陷阱和执行原理 GO语言defer和return 的执行顺序 深入理解Golang defer机制,直通面试 面试富途的时候,遇到了1.2的这个进阶问题,没回答出来。这种题简直是 噩梦\color{purple}{噩梦}噩梦,…...

谁说菜鸟不会数据分析,不用Python,不用代码也轻松搞定

作为一个菜鸟,你可能觉得数据分析就是做表格的,或者觉得搞个报表很简单。实际上,当前有规模的公司任何一个岗位如果没有数据分析的思维和能力,都会被淘汰,数据驱动分析是解决日常问题的重点方式。很多时候,…...

php mysql保健品购物商城系统

目 录 1 绪论 1 1.1 开发背景 1 1.2 研究的目的和意义 1 1.3 研究现状 2 2 开发技术介绍 2 2.1 B/S体系结构 2 2.2 PHP技术 3 2.3 MYSQL数据库 4 2.4 Apache 服务器 5 2.5 WAMP 5 2.6 系统对软硬件要求 6 …...

Vue3电商项目实战-首页模块6【22-首页主体-补充-vue动画、23-首页主体-面板骨架效果、4-首页主体-组件数据懒加载、25-首页主体-热门品牌】

文章目录22-首页主体-补充-vue动画23-首页主体-面板骨架效果24-首页主体-组件数据懒加载25-首页主体-热门品牌22-首页主体-补充-vue动画 目标: 知道vue中如何使用动画,知道Transition组件使用。 当vue中,显示隐藏,创建移除&#x…...

linux 使用

一、操作系统命令 1、版本命令:lsb_release -a 2、内核命令:cat /proc/version 二、debian与CentOS区别 debian德班和CentOS是Linux里两个著名的版本。两者的包管理方式不同。 debian安装软件是用apt(apt-get install),而CentOS是用yum de…...

基于遗传算法的微电网调度(风、光、蓄电池、微型燃气轮机)(Matlab代码实现)

💥💥💥💞💞💞欢迎来到本博客❤️❤️❤️💥💥💥🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清…...

方向导数与梯度下降

文章目录方向角与方向余弦方向角方向余弦方向导数定义性质梯度下降梯度下降法(Gradient descent)是一个一阶最优化算法,通常也称为最速下降法。 要使用梯度下降法找到一个函数的局部极小值,必须向函数上当前点对应梯度&#xff08…...

Java岗面试题--Java基础(日积月累,每日三题)

目录面试题一:Java中有哪些容器(集合类)?追问:Java中的容器,线程安全和线程不安全的分别有哪些?面试题二: HashMap 的实现原理/底层数据结构? JDK1.7 和 JDK1.8追问一&am…...

java基础—Volatile关键字详解

java基础—Volatile关键字详解 文章目录java基础—Volatile关键字详解并发编程的三大特性:volatile的作用是什么volatile如何保证有可见性volatile保证可见性在JMM层面原理volatile保证可见性在CPU层面原理可见性问题的例子volatile如何保证有序性单例模式使用volat…...

内存检测工具Sanitizers

Sanitizers介绍 Sanitizers 是谷歌开源的内存检测工具,包括AddressSanitizer、MemorySanitizer、ThreadSanitizer、LeakSanitizer。 Sanitizers是LLVM的一部分。 gcc4.8:支持Address和Thread Sanitizer。 gcc4.9:支持Leak Sanitizer和UBSani…...

Triton : OpenAI 开发的用于Gpu开发语言

Triton : OpenAI 开发的用于Gpu开发语言https://openai.com/blog/triton/1、介绍 https://openai.com/blog/triton/ 2、git地址 https://github.com/openai/triton 3、论文 http://www.eecs.harvard.edu/~htk/publication/2019-mapl-tillet-kung-cox.pdf SIMD : Single Inst…...

Python文件操作-代码案例

文章目录文件打开文件open写文件上下文管理器第三方库简单应用案例使用python生成二维码使用python操作excel程序员鼓励师学生管理系统文件 变量就在内存中,文件在硬盘中. 内存空间更小,访问速度快,成本贵,数据容易丢失,硬盘空间大,访问慢,偏移,持久化存储. \\在才是 \的含义…...

活动目录(Active Directory)管理,AD自动化

每个IT管理员几乎每天都在Active Directory管理中面临许多挑战,尤其是在管理Active Directory用户帐户方面。手动配置用户属性非常耗时、令人厌烦且容易出错,尤其是在大型、复杂的 Windows 网络中。Active Directory管理员和IT经理大多必须执行重复和世俗…...

Allegro如何使用Vertext命令修改丝印线段的形状操作指导

Allegro如何使用Vertext命令修改丝印线段的形状操作指导 在用Allegro画丝印线段的时候,如果画了一段不是自己需要形状的线段,无需删除重画,可以用Vertext命令直接编辑 如下图 修改前 修改后 具体操作如下 选择Edit...

Leetcode力扣秋招刷题路-0030

从0开始的秋招刷题路,记录下所刷每道题的题解,帮助自己回顾总结 30. 串联所有单词的子串 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。 s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。…...

基于Prometheus和k8s搭建监控系统

文章目录1、实验环境2、Prometheus介绍?3、Prometheus特点3.1 样本4、Prometheus组件介绍5、Prometheus和zabbix对比分析6、Prometheus的几种部署模式6.1 基本高可用模式6.2 基本高可用远程存储6.3 基本HA 远程存储 联邦集群方案7、Prometheus的四种数据类型7.1 C…...

类和对象(下)

类和对象(下)再谈构造函数构造函数体赋值初始化列表explicit关键字static成员静态成员的特性友元友元函数友元类成员函数做友元内部类匿名对象编译器的一些优化再谈构造函数 构造函数体赋值 在创建对象的时候编译器会调用构造函数给对象中的成员变量一…...

达梦数据库单机部署

一、安装前准备 1. 安装环境 操作系统:redhat7.9 达梦数据库版本:V8 内存:2G CPU:x86_64 2. 新建用户组和用户 groupadd dinstall useradd -g dinstall -m -d /home/dmdba -S /bin/bash dmdba passwd dmdba3. 配置参数 vi /etc/security/limits.conf #在末尾添加以下内…...

从零到一学习Flutter——(二)状态和路由

背景 前文提到了Widget的状态,在Flutter中一切都是Widget,那么由Widget组成的页面,会有很多复杂的父子关系,要想交互友好,则需要这些Widget进行通讯,也就是所谓的状态管理。 同时在了解了布局之后,我们会写出很多的页面,那么在这些页面切换,也是一个很重要的能力。 …...

TC358774XBG/TC358775XBG替代方案|CS5518替代TC358774XBG/TC358775XBG设计DSI转LVSD设计资料

TC358774XBG/TC358775XBG替代方案|CS5518替代TC358774XBG/TC358775XBG设计DSI转LVSD设计资料 TC358774XBG/TC358775XBG 芯片的主要功能是作为 DSI - LVDS 通信协议桥接,主芯片的视频数据可通过 DSI 链路流 出,以驱动兼容 LVDS 的显示板。换句话说&#x…...

【杂谈】-递归进化:人工智能的自我改进与监管挑战

递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统

医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...

centos 7 部署awstats 网站访问检测

一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats&#xff0…...

ffmpeg(四):滤镜命令

FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

AI书签管理工具开发全记录(十九):嵌入资源处理

1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...

10-Oracle 23 ai Vector Search 概述和参数

一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...

iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈

在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...

系统掌握PyTorch:图解张量、Autograd、DataLoader、nn.Module与实战模型

本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文通过代码驱动的方式,系统讲解PyTorch核心概念和实战技巧,涵盖张量操作、自动微分、数据加载、模型构建和训练全流程&#…...

xmind转换为markdown

文章目录 解锁思维导图新姿势:将XMind转为结构化Markdown 一、认识Xmind结构二、核心转换流程详解1.解压XMind文件(ZIP处理)2.解析JSON数据结构3:递归转换树形结构4:Markdown层级生成逻辑 三、完整代码 解锁思维导图新…...