defe 有名返回值跟无名返回值区别(重要)

 defer 的执行顺序

答案
  • 多个 defer 语句,遵从后进先出(Last In First Out,LIFO)的原则,最后声明的 defer 语句,最先得到执行。
  • defer 在 return 语句之后执行,但在函数退出之前,defer 可以修改返回值。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func test() int {
i := 0
defer func() {
fmt.Println("defer1")
}()
defer func() {
i += 1
fmt.Println("defer2")
}()
return i
}

func main() {
fmt.Println("return", test())
}
// defer2
// defer1
// return 0

这个例子中,可以看到 defer 的执行顺序:后进先出。但是返回值并没有被修改,这是由于 Go 的返回机制决定的,执行 return 语句后,Go 会创建一个临时变量保存返回值,因此,defer 语句修改了局部变量 i,并没有修改返回值。那如果是有名的返回值呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func test() (i int) {
i = 0
defer func() {
i += 1
fmt.Println("defer2")
}()
return i
}

func main() {
fmt.Println("return", test())
}
// defer2
// return 1

这个例子中,返回值被修改了。对于有名返回值的函数,执行 return 语句时,并不会再创建临时变量保存,因此,defer 语句修改了 i,即对返回值产生了影响

再看下面一个题

import "fmt"

var name string = "go"

func myfunc() string {
    defer func() {
        name = "python"
    }()

    fmt.Printf("myfunc 函数里的name:%s\n", name)
    return name
}

func main() {
    myname := myfunc()
    fmt.Printf("main 函数里的name: %s\n", name)
    fmt.Println("main 函数里的myname: ", myname)
}

输出如下

myfunc 函数里的name:go
main 函数里的name: python
main 函数里的myname:  go

来一起理解一下这段代码,第一行很直观,name 此时还是全局变量,值还是go

第二行也不难理解,在 defer 里改变了这个全局变量,此时name的值已经变成了 python

重点在第三行,为什么输出的是 go ?

解释只有一个,那就是 defer 是return 后才调用的。所以在执行 defer 前,myname 已经被赋值成 go 了

Q11 如何交换 2 个变量的值?

答案

Q12 Go 语言 tag 的用处?

答案

Q13 如何判断 2 个字符串切片(slice) 是相等的?

答案

Q14 字符串打印时,%v 和 %+v 的区别

答案

Q15 Go 语言中如何表示枚举值(enums)

答案

Q16 空 struct{} 的用途

答案

专题: 

本文发表于 2020-09-04,最后修改于 2022-02-25。

本站永久域名「 geektutu.com 」,也可搜索「 极客兔兔 」找到我。


上一篇 « Go 语言笔试面试题汇总下一篇 » Go 语言笔试面试题(实现原理)

赞赏支持
   

推荐阅读

 
 
 
6 条评论
未登录用户
@lisgroup
 
lisgroup发表于超过 1 年前

新手有两个问题想请教下:

1. Q7 什么是 rune 类型

代码中 []rune("Go语言") 意思是使用 rune 强制转换类型是吧?

2. Q13 如何判断 2 个字符串切片(slice) 是相等的?

其中代码块中的第10行

b = b[:len(a)]

这么操作的目的是什么?保证数组切片一样长吗?

@geektutu
 
geektutu发表于超过 1 年前

@lisgroup

第一个问题,你的理解是对的,字符串类型转换为 rune 切片类型。

第二个问题,这种写法是为了优化边界检查从而提升运行时效率的。在go语言中称之为 Bounds Check Elimination,简称为 BCE。简言解释下,如果没有这一句,在运行时,Go 语言每次都会对 b[i] 做边界检查,看看是否越界了,如果越界了,就 panic。但是如果加上这一句,Go语言在编译时,能够做一些简单的静态分析,发现 b[i] 是不可能越界的,编译时就能将没必要的边界检查给优化了,那么运行时,就不会对 b[i] 做边界检查,从而提升运行时效率。

这篇文章提供了更多的示例,帮助理解。Bounds Check Elimination - go101.org

@givetimetolife
 
givetimetolife发表于超过 1 年前

_ = b[:len(a)]

@geektutu
 
geektutu发表于超过 1 年前

@givetimetolife 我觉得这种写法应该也是OK的,Go 的切片底层对应的数组是一样的,切片只是改变指针的位置,这一步检查几乎是没有损耗的。

@zzhaolei
 
zzhaolei发表于大约 1 年前

2. Q13 如何判断 2 个字符串切片(slice) 是相等的?

针对这个中的b = b[:len(a)],使用go 1.15.6编译,已经没有效果了,在len判断的时候就已经做了优化

@geektutu
 
geektutu发表于大约 1 年前

@zzhaolei 感谢指出问题,新版本将 b = b[:len(a)] 去掉后,也不会产生边界检查(Bounds Check)了。

是否产生边界检查,可以用下面的命令验证。

go build -gcflags="-d=ssa/check_bce/debug=1" main.go

例如针对下面的例子,如果 s[2] 没有越界,那么 s[1] 和 s[0] 肯定是合法,因此只需要对 s[2] 做一次边界检查即可:

package main

func f(s []int) {
    _ = s[2]
    _ = s[1]
    _ = s[0]
}

func main() {}
go build -gcflags="-d=ssa/check_bce/debug=1" main.go
# command-line-arguments
./main.go:4:7: Found IsInBounds
posted @ 2022-02-26 09:51  技术颜良  阅读(371)  评论(0编辑  收藏  举报