原文链接:http://www.zhoubotong.site/post/50.html

defer语句用于延迟函数调用,每次会把一个函数压入栈中,函数返回前再把延迟的函数取出并执行。延迟函数可以有参数:

  • 延迟函数的参数在defer语句出现时就已确定下来(传值的就是当前值)

  • return先赋值(对于命名返回值),然后执行defer,最后函数返回

  • 延迟函数执行按后进先出顺序执行

  • 延迟函数可操作主函数的变量名返回值(修改返回值)

  • defer后面的表达式可以是func或者是method的调用,如果defer的函数为nil,则会panic

日常开发中,使用不当很容易造成意外的“坑”。下面我整理了下常规使用场景下,defer的问题可能的踩坑汇总。

释放资源

defer语句用于延迟函数调用,每次会把一个函数压入栈中,函数返回前再把延迟的函数取出并执行。延迟函数可以有参数:

  • 延迟函数的参数在defer语句出现时就已确定下来(传值的就是当前值);

  • 延迟函数执行按后进先出顺序执行;

  • 延迟函数可操作主函数的具名返回值(修改返回值);

defer 语句正好是在函数退出时执行的语句,所以使用 defer 能非常方便地处理资源释放、句柄关闭等问题。

package main
import (
"fmt"
"os"
)
func fileSize(filename string) int64 {
f, err := os.Open(filename)
if err != nil {
return 0
}
// 延迟调用Close, 此时Close不会被调用
defer f.Close()
info, err := f.Stat()
if err != nil {
// defer机制触发, 调用Close关闭文件
return 0
}
size := info.Size()
// defer机制触发, 调用Close关闭文件
return size
}
func main() {
fmt.Println(fileSize("demo.txt"))
}

变量捕获

defer中的变量会被提前捕获,后续的修改不会影响到已捕获的值,举个例子:

package main
import (
"fmt"
)
func main() {
i := 0
defer fmt.Println("Defer运行值:", i)
i = 10 // 这里虽然修改了值,但是不会影响上面的i值
fmt.Println("最后输出值:", i)
}

结果defer语句中打印的值是修改前的值。:

最后输出值: 10

Defer运行值: 0

变量名返回值

在defer中修改具体变量名返回值时,会影响到函数的实际返回值,继续举个例子:

package main

import (
"fmt"
) func ShowDefer() {
fmt.Println("最后输出值:", deferValue())
}
func deferValue() (ret int) { // 注意这里返回res变量值
ret = 0 defer func() { // 会直接修改栈中对应的返回值
ret += 10
fmt.Println("Defer 运行值:", ret)
}()
ret = 2
fmt.Println("Ret重置值:", ret)
return //返回的ret最后是 其实是本次2+上面的ret+=10的结果
} func main() {
ShowDefer()
} //Ret重置值: 2
//Defer 运行值: 12
//最后输出值: 12

非变量名返回值

当函数为非具体名返回值时,defer无法影响返回值(因在return时,对应返回值已存入栈中),继续举个例子:

package main

import (
"fmt"
) func ShowDefer() {
fmt.Println("最后输出值:", deferValue())
}
func deferValue() int { // 非命名变量返回
ret := 0
defer func() {
ret += 10
fmt.Println("Defer 运行值:", ret)
}()
ret = 2
return ret // 这里直接返回ret2
} func main() {
ShowDefer()
} //Defer 运行值: 12
//最后输出值: 2

经过上面的实践理解,我们来看下下面的笔试题:

笔试题一

package main

import "fmt"

func f() (result int) {
defer func() {
result *= 7
}()
return 3
}
func main() {
fmt.Println(f())
}

问题解析:这里return先给result赋值为3,之后执行defer,result变为21,最后返回21。

笔试题二

package main

import "fmt"

func f() int {
result := 3
defer func() {
result *= 7
}()
return result
} func main() {
fmt.Println(f())
} 

问题解析:这里return确定返回值3,之后defer才修改result,最后函数返回return确定的返回值3。

笔试题三

package main

import "fmt"
// 多个defer
func multiDefer() {
for i := 3; i > 0; i-- {
defer func(n int) {
fmt.Print(n, " ")
}(i)
} for i := 33; i > 30; i-- {
defer fmt.Print(i, " ")
}
} func main() {
multiDefer()
}  

问题解析:多个defer函数,按顺序逆序执行,这里输出31 32 33 1 2 3 。

笔试题四

package main

import "fmt"

var fun func() string

func main() {
fmt.Println("hello monkey")
defer fun()
} 

问题解析:由于这里的defer指定的func为nil,所以会panic 。

笔试题五

package main

import "fmt"

func main() {
for i := 3; i > 0; i-- {
defer func() {
fmt.Print(i, " ")
}()
}
} 

问题解析:这里是极度容易踩坑的地方,由于defer这里调用的func没有参数,等执行的时候,i已经为0(按3 2 1逆序,最后一个i=1时,i--的结果最后是0),所以这里输出3个0 。

如果还不太好理解?

package main

import "fmt"

func main() {
for i := 3; i > 1; i-- { // 循环满足条件的是 3 2,
defer func() { // 因为func 没有参数,defer运行最后i--即 2-- 结果为 1
fmt.Print(i, " ") // 循环2次 结果均为 1
}()
}
}//输出 1 1  

按照常规的思维理解应该是这样:

package main

import "fmt"

func main() {
for i := 3; i > 0; i-- {
defer func(i int) {
fmt.Print(i, " ")
}(i)
}
} 
感兴趣的朋友可以细细品下。

  

  

  

  

 

golang的defer踩坑汇总的更多相关文章

  1. golang协程踩坑记录

    1.主线程等待多个协程执行完毕后,再执行下面的程序.golang提供了一个很好用的工具. sync.WaitGroup下面是个简单的例子. 执行结果: 2.主线程主动去结束已经启动了的多个协程.执行结 ...

  2. Hyperledger Fabric 踩坑汇总

    搭建基础环境 阿里云安装出现的一些问题解决 1. [signal SIGSEGV: segmentation violation code=0x1 addr=xxx pc=xxx] 类似的错误:原始错 ...

  3. vscode 踩坑汇总

    gopls 提示 update 将 "go.useLanguageServer": true 改为 "go.useLanguageServer": false

  4. 微信小程序踩坑集合

    1:官方工具:https://mp.weixin.qq.com/debug/w ... tml?t=1476434678461 2:简易教程:https://mp.weixin.qq.com/debu ...

  5. golang 学习过程中踩的坑

    目录 [他人总结] 首字母大写才是对外可见的 包的初始化函数顺序问题 DB 连接泄漏问题 err 常用写法 goroutine 内的变量 指针可能是 nil 多层 map 未初始化 [他人总结] ht ...

  6. navicate 连接mysql8.0,个人踩坑问题汇总

    navicate 连接mysql8.0,个人踩坑问题汇总本文目录:1:安装mysql8.0新增全新验证方式,安装如果不修改mysql连接不上2:mysql启动命令问题3:navicate 运程连接My ...

  7. Nuxt.js的踩坑指南(常见问题汇总)

    本文会不定期更新在nuxt.js中遇到的问题进行汇总.转发请注明出处,尊重作者,谢谢! 强烈推荐作者文档版踩坑指南,点击跳转踩坑指南 在Nuxt的官方文档中,中文文档和英文文档都存在着不小的差异. 1 ...

  8. Spark踩坑记——共享变量

    [TOC] 前言 Spark踩坑记--初试 Spark踩坑记--数据库(Hbase+Mysql) Spark踩坑记--Spark Streaming+kafka应用及调优 在前面总结的几篇spark踩 ...

  9. Spark踩坑记——从RDD看集群调度

    [TOC] 前言 在Spark的使用中,性能的调优配置过程中,查阅了很多资料,之前自己总结过两篇小博文Spark踩坑记--初试和Spark踩坑记--数据库(Hbase+Mysql),第一篇概况的归纳了 ...

随机推荐

  1. SQL语言学习-DQL条件查询

    条件查询 1. where子句后跟条件 2. 运算符 * > .< .<= .>= .= .<> * BETWEEN...AND * IN( 集合) * LIKE: ...

  2. docker基础_Dockerfile

    Dockerfile []: https://docs.docker.com/language/python/build-images/ "docker官方文档" 以python为 ...

  3. 邮件html页编写指南

    前言 写过邮件的html一般都用table来布局,为什么呢?原因是大多数的邮件客户端(比如Outlook和Gmail),会过滤经过多次的邮件编写实践及度娘的指导,我发现,编写自制兼容outlook与f ...

  4. MySQL Router重装后重新连接集群进行引导出现的——此主机中之前已配置过的问题

    问题出现的前因: 因为重新安装了MySQL Router,然后打算重新连接上目标集群进行MySQL Router的初始化引导,结果报错了! [root@linux666 system]# mysqlr ...

  5. 【CSAPP】Attack Lab实验笔记

    attacklab这节玩的是利用一个字符串进行缓冲区溢出漏洞攻击,就小时候想象中黑客干的事儿. 做题的时候好几次感叹这些人的脑洞,"这都可以攻击?还能这么注入?这还可能借力打力?" ...

  6. 【CSAPP】Data Lab实验笔记

    前天讲到要刚CSAPP,这一刚就是两天半.CSAPP果然够爽,自带完整的说明文档,评判程序,辅助程序.样例直接百万组走起,管饱! datalab讲的是整数和浮点数怎么用二进制表示的,考验的是用基本只用 ...

  7. VMware安装Ubuntu20(图文教程,超详细)

    VMware安装Ubuntu20(图文教程,超详细) 此文讲述使用 VMware 工具安装 Ubuntu 系列虚拟机,不同位数和不同版本的 Ubuntu 安装过程相差无几,这里以 Ubuntu20 6 ...

  8. 2.SSH协议常见问题排错

    一.SSH登录linux服务器密码验证很慢 现象:ssh登录服务器后,输入密码时,验证要等10秒左右,很慢.登录上去后速度正常,这种情况主要有两种可能的原因: 1. DNS反向解析的问题 OpenSS ...

  9. .NET混合开发解决方案7 WinForm程序中通过NuGet管理器引用集成WebView2控件

    系列目录     [已更新最新开发文章,点击查看详细] WebView2组件支持在WinForm.WPF.WinUI3.Win32应用程序中集成加载Web网页功能应用.本篇主要介绍如何在WinForm ...

  10. .NET混合开发解决方案11 WebView2加载的网页中JS调用C#方法

    系列目录     [已更新最新开发文章,点击查看详细] WebView2控件应用详解系列博客 .NET桌面程序集成Web网页开发的十种解决方案 .NET混合开发解决方案1 WebView2简介 .NE ...