Golang中的defer

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

情形1

go 复制代码
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
    
}

结果为:

go 复制代码
打印后打印中打印前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机制底层,是用链表实现的一个栈)

再如:

go 复制代码
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)

}

结果为:

go 复制代码
123
456
888
999

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


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

go 复制代码
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)

}

输出为:

go 复制代码
+1后的i: 1
再+1后的i: 2
再再+1后的i: 3
+666后的i为: 669
第三个defer 669
第二个defer 669
第一个defer 669

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

go 复制代码
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)

}

输出为:

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

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

go 复制代码
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)

}

输出为:

go 复制代码
+1后的i: 1
再+1后的i: 2
再再+1后的i: 3
+666后的i为: 669
第三个defer 2
第二个defer 1
第一个defer 0

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

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

等同于:

go 复制代码
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内外同时操作该参数)

go 复制代码
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)
}

输出为:

go 复制代码
+1后的i: 1
再+1后的i: 2
再再+1后的i: 3
+666后的i为: 669
第三个defer 669
第二个defer 669
第一个defer 669

作如下修改:

go 复制代码
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)
}

输出为:

go 复制代码
+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面试题答案与解析


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

go 复制代码
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
}

输出为:

go 复制代码
in foo: 1000
in defer : 0
in main func: 1024

如果改为:

go 复制代码
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
}

输出为:

go 复制代码
in foo: 1000
in defer : 1000
in main func: 1024

也可见,

go 复制代码
defer fmt.Println("in defer :", i)

相当于

go 复制代码
defer func(k int) {
		fmt.Println(k)
    }(i)

go 复制代码
func f(k int){
	fmt.Println(k)
}

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

而如

go 复制代码
	defer func() {
		fmt.Println("in defer :", i)
    }()

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


defer会影响返回值吗?

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

go 复制代码
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
}

输出为:

go 复制代码
0
40
50

再如:

go 复制代码
func main() {

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

}

func foo() map[string]string {

    m := map[string]string{}

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

    return m
}

输出为:

go 复制代码
foo return : map[a:b]

又如:

go 复制代码
package main


import "fmt"

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

func foo() int {
	i := 0

	defer func() {
		i = 10086
	}()

	return i + 5
}

输出为:

go 复制代码
foo return : 5

若作如下修改:

go 复制代码
func foo() (i int) {
	i = 0
	defer func() {
		i = 10086
	}()

	return i + 5
}

则返回为:

go 复制代码
foo return : 10086

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

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

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

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


参考:

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

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

本文由mdnice多平台发布

相关推荐
㳺三才人子6 小时前
初探 Flask
后端·python·flask·html
星栈独行6 小时前
我在 Rust 全栈项目里用 JWT 做无状态认证
开发语言·后端·rust·前端框架·开源·github·web
Java爱好狂.7 小时前
Java程序员体系化学习路线(2026最新版)
java·后端·java面试·java架构师·java程序员·java八股文·java学习路线
陈随易7 小时前
Redis 8.8发布,一定要更新
前端·后端·程序员
装不满的克莱因瓶7 小时前
SpringBoot 如何将 lib 目录中jar包打包进最终的jar包里面
spring boot·后端·maven·jar·mvn
ltl8 小时前
Transformer 原论文实验结果:为什么 28.4 BLEU 足以改写路线图
后端
excel9 小时前
为什么我推荐使用 Termius:现代 SSH 工具的完整体验
前端·后端
卷毛的技术笔记9 小时前
Java后端硬核实战:用Spring AI Alibaba+Redis给LLM装上“超强记忆中枢”
java·人工智能·redis·后端·spring·ai·系统架构
IT_陈寒10 小时前
Java的Optional差点让我掉坑里,这几个坑你别踩
前端·人工智能·后端
子兮曰11 小时前
Harness 驾驭工程深度教程:从 AGENTS.md 到全链路 AI 编码基础设施
前端·后端·ai编程