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

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 语言笔试面试题(实现原理)

赞赏支持 defe 有名返回值跟无名返回值区别(重要) defe 有名返回值跟无名返回值区别(重要) defe 有名返回值跟无名返回值区别(重要) defe 有名返回值跟无名返回值区别(重要)

推荐阅读

  Go2 新特性简明教程

发表于2019-08-15, 阅读约17分钟

  TensorFlow 2 中文文档 - 过拟合与欠拟合

发表于2019-07-12, 阅读约19分钟

  Pandas 数据处理(三) - Cheat Sheet 中文版

发表于2019-06-16, 阅读约4分钟

                   6 条评论 未登录用户 defe 有名返回值跟无名返回值区别(重要)   lisgroup发表于超过 1 年前

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

1. Q7 什么是 rune 类型

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

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

其中代码块中的第10行

b = b[:len(a)]

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

defe 有名返回值跟无名返回值区别(重要)   geektutu发表于超过 1 年前

@lisgroup

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

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

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

defe 有名返回值跟无名返回值区别(重要)   givetimetolife发表于超过 1 年前

_ = b[:len(a)]

defe 有名返回值跟无名返回值区别(重要)   geektutu发表于超过 1 年前

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

defe 有名返回值跟无名返回值区别(重要)   zzhaolei发表于大约 1 年前

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

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

defe 有名返回值跟无名返回值区别(重要)   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

动手写分布式缓存 - GeeCache第五天 分布式节点

61 评论 ● 1月前

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

yinhuanyi —— 博主,Group的getFromPeer方法返回的ByteView必须是指针,好像写错了 ``` func (g *Group) getFromPeer(peer PeerGetter, key string) (*ByteView, error) { bytes, err := peer.Get(g.name, key) if err != nil { return &ByteView{}, err } return &ByteView{b: bytes}, nil } ```

 

上一篇:Go语言学习12-切片扩容


下一篇:ORA-4031相关脚本(Doc ID 430473.1)