defe 有名返回值跟无名返回值区别(重要)
defer 的执行顺序
答案- 多个 defer 语句,遵从后进先出(Last In First Out,LIFO)的原则,最后声明的 defer 语句,最先得到执行。
- defer 在 return 语句之后执行,但在函数退出之前,defer 可以修改返回值。
例如:
1
|
func test() int {
|
这个例子中,可以看到 defer 的执行顺序:后进先出。但是返回值并没有被修改,这是由于 Go 的返回机制决定的,执行 return 语句后,Go 会创建一个临时变量保存返回值,因此,defer 语句修改了局部变量 i,并没有修改返回值。那如果是有名的返回值呢?
1
|
func test() (i int) {
|
这个例子中,返回值被修改了。对于有名返回值的函数,执行 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{} 的用途
答案上一篇 « Go 语言笔试面试题汇总下一篇 » Go 语言笔试面试题(实现原理)
推荐阅读
发表于2019-08-15, 阅读约17分钟
发表于2019-07-12, 阅读约19分钟
发表于2019-06-16, 阅读约4分钟
新手有两个问题想请教下:
1. Q7 什么是 rune 类型
代码中 []rune("Go语言")
意思是使用 rune
强制转换类型是吧?
2. Q13 如何判断 2 个字符串切片(slice) 是相等的?
其中代码块中的第10行
b = b[:len(a)]
这么操作的目的是什么?保证数组切片一样长吗?
第一个问题,你的理解是对的,字符串类型转换为 rune 切片类型。
第二个问题,这种写法是为了优化边界检查从而提升运行时效率的。在go语言中称之为 Bounds Check Elimination
,简称为 BCE。简言解释下,如果没有这一句,在运行时,Go 语言每次都会对 b[i] 做边界检查,看看是否越界了,如果越界了,就 panic。但是如果加上这一句,Go语言在编译时,能够做一些简单的静态分析,发现 b[i] 是不可能越界的,编译时就能将没必要的边界检查给优化了,那么运行时,就不会对 b[i] 做边界检查,从而提升运行时效率。
这篇文章提供了更多的示例,帮助理解。Bounds Check Elimination - go101.org
_ = b[:len(a)]
@givetimetolife 我觉得这种写法应该也是OK的,Go 的切片底层对应的数组是一样的,切片只是改变指针的位置,这一步检查几乎是没有损耗的。
2. Q13 如何判断 2 个字符串切片(slice) 是相等的?
针对这个中的b = b[:len(a)]
,使用go 1.15.6
编译,已经没有效果了,在len判断
的时候就已经做了优化
@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