首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >正确理解 golang 函数变量的作用域, 管你 defer 不 defer

正确理解 golang 函数变量的作用域, 管你 defer 不 defer

作者头像
老麦
发布于 2023-02-25 11:43:31
发布于 2023-02-25 11:43:31
90600
代码可运行
举报
文章被收录于专栏:Go与云原生Go与云原生
运行总次数:0
代码可运行

你以为面试中的 defer 是在考 defer 吗?并不是,其实是在考 函数变量的作用域

以下这是 go语言爱好者 97 期的一道题目。要求很简单, 代码执行 i, j 的值分别是什么。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func Test_Demo(t *testing.T) {
	i := 10
	j := hello(&i)
	fmt.Println(i, j)
}

func hello(i *int) int {
	defer func() {
		*i = 19
	}()
	return *i
}

这道题虽然代码少, 但是考点还是蛮多的

  1. 核心: 函数变量作用域
  2. defer 执行时间
  3. 闭包
  4. 指针

知识点

这里面所有的内容都可以在 Effective Go 中解决

贪婪算法

什么是贪婪算法, 就是找到局部最优解, 合并后就是全局最优解。

怎么找局部最优解, 就是要 对事情进行抽象,掌握事情的本质 。

defer 延迟执行

defer 就是语句进行压栈(FILO)处理, 延迟到 在函数 return 之前执行 执行。本身没什么难点。其设计目的也很明确就是为了 解决资源释放 的问题。

  1. openclose 写在一起, 语意更直观。
  2. 解决因为错误退出,导致而 无法或忘记 释放资源

Effective Go 中对 defer 的概述。 It’s an unusual but effective way to deal with situations such as resources that must be released regardless of which path a function takes to return. 这是一种不寻常但有效的方法来处理诸如必须释放资源的情况,而不管函数采用哪条路径返回。

因此 defer 有什么好考的, 而且实际场景代码也不会那样写(违反了可读性的这一基本之准则)。

所以通常面试中有 defer 的问题都不是在考 defer , 只不过是披上了 defer 的狼皮。

函数及返回值

其实 go 中关于函数返回花样还是挺多的。

  1. 命名的/匿名的 返回值 func NamedResult(i, j int) (x int)
  2. 带参数不带参数的 return return

感觉和 golang 本身的代码可读性的的理念有一点冲突。就像为什么不支持三元运算符一样。其实这样本身也没有什么, 就是一两个 死记硬背 的知识点而已。

但是遇到了 defer, 闭包, 指针 中对变量有操作, 那么问题可能就大了。

如果对 函数变量的作用域 理解不清楚的话, 就容易掉坑。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

// 命名结果
func NamedResult(i, j int) (x int) {
	x = i + j
	// 默认返回
	return
}

// 匿名结果
func UnnamedResult(i, j int) int {
	// 指定返回
	return i + j
}

我们开启汇编, 查看一下函数过程

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
go tool compile -N -l -S  main.go

从汇编结果可以看到:

  1. 虽然我们在 UnnamedResult 代码中没有显式的提供返回值的变量名, 但是 golang 自动为我们生成了一个叫 ~r2 变量名, 其 等价于 NamedResult 函数中的变量x
  2. 汇编中 RET后没有带任何参数
    • 所有与结果有关的操作都标记了 (SP) , ex: MOVQ AX, "".~r2+24(SP)

既然如此, 我们就将所有函数的写法全部统一, 不再区分 命名的、 匿名的 , 默认的, 指定的

  1. 命名返回值
  2. return 指定结果
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func ReformResult(i, j int) (r2 int) {
	r2 = i + j
	return r2
}

这样看起来, 整个函数就清晰的多了。

实战练习一下

根据之前所说, 我们这里来对函数做一下整形手术。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func Test_reformDemo(t *testing.T) {
	i := 10
	j := reformHello(&i)
	fmt.Println(i, j) // 19 10
}

// hello 原函数
func hello(i *int) int {
	defer func() {
		*i = 19
	}()
	return *i
}

// reformHello 整形函数
// reform 1. 匿名变命名
func reformHello(i *int) (_x int) {
	// reform 2. return 拆分
	// reform 2.1 显式赋值
	_x = *i  // _x=10

	// reform 3. defer 在返回前执行 
	func() { *i = 19 }()  // *i=19

	// reform 2.2 显式返回
	return _x // _x=10
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-06-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 熊猫云原生Go 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验