Go语言学习——彻底弄懂return和defer的微妙关系

科工人 等级 983 0 0

疑问

前面在函数篇里介绍了Go语言的函数是支持多返回值的。

只要在函数体内,对返回值赋值,最后加上return就可以返回所有的返回值。

最近在写代码的时候经常遇到在return后,还要在defer里面做一些收尾工作,比如事务的提交或回滚。所以想弄清楚这个return和defer到底是什么关系,它们谁先谁后,对于最后返回值又有什么影响呢?

动手验证

了解下来,问题比我想的要复杂,不信你先看看下面这段代码输出结果是啥

package main

import "fmt"

func main()  {
    fmt.Println("f1 result: ", f1())
    fmt.Println("f2 result: ", f2())
}

func f1() int {
    var i int
    defer func() {
        i++
        fmt.Println("f11: ", i)
    }()

    defer func() {
        i++
        fmt.Println("f12: ", i)
    }()

    i = 1000
    return i
}

func f2() (i int) {
    defer func() {
        i++
        fmt.Println("f21: ", i)
    }()

    defer func() {
        i++
        fmt.Println("f22: ", i)
    }()

    i = 1000
    return i
} 

最后的执行结果如下

f12:  1001
f11:  1002
f1 result:  1000
f22:  1001
f21:  1002
f2 result:  1002 

f1函数:

进入该函数,因为没有指定返回值变量,需要先声明i变量,因为是int类型,如果没有赋值,该变量初始化值为0,之后执行i=1000的赋值操作,然后执行return语句,返回i的值。

真正返回之前还要执行defer函数部分,两个defer函数分别针对i进行自增操作,i的值依次为1001和1002

f2函数:

进入该函数,因为已经定义好了返回值变量即为i,然后直接赋值i=1000,再返回i的值。

同样的,也要在真正返回i前,执行两个defer函数,同样i依次自增得到1001和1002。

问题的关键是为什么无名参数返回的值是1000,其并未收到defer函数对于i自增的影响;而有名函数在执行defer后,最后返回的i值为1002。

网上找了一些原因,提到一个结论

原因就是return会将返回值先保存起来,对于无名返回值来说,
保存在一个临时对象中,defer是看不到这个临时对象的;
而对于有名返回值来说,就保存在已命名的变量中。 

看到这个结论,我想试试通过打印i的地址值是否可以看出一些端倪和线索

为此在两个函数中添加了打印i的地址信息

package main

import "fmt"

func main()  {
    fmt.Println("f1 result: ", f1())
    fmt.Println("f2 result: ", f2())
}

func f1() int {
    var i int
    fmt.Printf("i: %p \n", &i)
    defer func() {
        i++
        fmt.Printf("i: %p \n", &i)
        fmt.Println("f11: ", i)
    }()

    defer func() {
        i++
        fmt.Printf("i: %p \n", &i)
        fmt.Println("f12: ", i)
    }()

    i = 1000
    return i
}

func f2() (i int) {
    fmt.Printf("i: %p \n", &i)
    defer func() {
        i++
        fmt.Printf("i: %p \n", &i)
        fmt.Println("f21: ", i)
    }()

    defer func() {
        i++
        fmt.Printf("i: %p \n", &i)
        fmt.Println("f22: ", i)
    }()
    i = 1000
    return i
} 

程序输出结果为

i: 0xc000090000 
i: 0xc000090000 
f12:  1001
i: 0xc000090000 
f11:  1002
f1 result:  1000
i: 0xc00009a008 
i: 0xc00009a008 
f22:  1001
i: 0xc00009a008 
f21:  1002
f2 result:  1002 

从这个结果可以看出,无论是f1还是f2函数中,变量i的地址全程没有改变过。

所以对于上面这个结论我似乎懂了,但是还是有些模糊,return保存在一个临时对象中,defer看不到这个临时变量,但是i的值为什么能够在1000的基础上累加呢?

拨开云雾

如果要从根本解决这个疑问,最好能够看看这段程序执行,背后的内存是如何分配的。

这时候想到了前几天看书里提到的可以通过命令将go语言转为汇编语言。

为了简化问题,将源代码修改为

package main

import "fmt"

func main()  {
    fmt.Println("f1 result: ", f1())
    fmt.Println("f2 result: ", f2())
}

func f1() int {
    var i int
    defer func() {
        i++
        fmt.Println("f11: ", i)
    }()

    i = 1000
    return i
}

func f2() (i int) {
    defer func() {
        i++
        fmt.Println("f21: ", i)
    }()
    i = 1000
    return i
} 

通过执行命令go tool compile -S test.go得到汇编代码如下

os.(*File).close STEXT dupok nosplit size=26 args=0x18 locals=0x0
    ...
    0x0000 00000 (test.go:5)    TEXT    "".main(SB), ABIInternal, $136-0
    0x0000 00000 (test.go:5)    MOVQ    (TLS), CX
    0x0009 00009 (test.go:5)    LEAQ    -8(SP), AX
    0x000e 00014 (test.go:5)    CMPQ    AX, 16(CX)
    0x0012 00018 (test.go:5)    JLS 315
    0x0018 00024 (test.go:5)    SUBQ    $136, SP
    0x001f 00031 (test.go:5)    MOVQ    BP, 128(SP)
    0x0027 00039 (test.go:5)    LEAQ    128(SP), BP
    0x002f 00047 (test.go:5)    FUNCDATA    $0, gclocals·7d2d5fca80364273fb07d5820a76fef4(SB)
    ...
"".f1 STEXT size=145 args=0x8 locals=0x28
    0x0000 00000 (test.go:10)   TEXT    "".f1(SB), ABIInternal, $40-8
    0x0000 00000 (test.go:10)   MOVQ    (TLS), CX
    0x0009 00009 (test.go:10)   CMPQ    SP, 16(CX)
    0x000d 00013 (test.go:10)   JLS 135
    0x000f 00015 (test.go:10)   SUBQ    $40, SP
    0x0013 00019 (test.go:10)   MOVQ    BP, 32(SP)
    0x0018 00024 (test.go:10)   LEAQ    32(SP), BP
    0x001d 00029 (test.go:10)   FUNCDATA    $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x001d 00029 (test.go:10)   FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x001d 00029 (test.go:10)   FUNCDATA    $3, gclocals·9fb7f0986f647f17cb53dda1484e0f7a(SB)
    0x001d 00029 (test.go:10)   PCDATA  $2, $0
    0x001d 00029 (test.go:10)   PCDATA  $0, $0
    0x001d 00029 (test.go:10)   MOVQ    $0, "".~r0+48(SP)
    0x0026 00038 (test.go:11)   MOVQ    $0, "".i+24(SP)
    0x002f 00047 (test.go:12)   MOVL    $8, (SP)
    0x0036 00054 (test.go:12)   PCDATA  $2, $1
    0x0036 00054 (test.go:12)   LEAQ    "".f1.func1·f(SB), AX
    0x003d 00061 (test.go:12)   PCDATA  $2, $0
    0x003d 00061 (test.go:12)   MOVQ    AX, 8(SP)
    0x0042 00066 (test.go:12)   PCDATA  $2, $1
    0x0042 00066 (test.go:12)   LEAQ    "".i+24(SP), AX
    0x0047 00071 (test.go:12)   PCDATA  $2, $0
    0x0047 00071 (test.go:12)   MOVQ    AX, 16(SP)
    0x004c 00076 (test.go:12)   CALL    runtime.deferproc(SB)
    0x0051 00081 (test.go:12)   TESTL   AX, AX
    0x0053 00083 (test.go:12)   JNE 119
    0x0055 00085 (test.go:17)   MOVQ    $1000, "".i+24(SP)
    0x005e 00094 (test.go:18)   MOVQ    $1000, "".~r0+48(SP)
    0x0067 00103 (test.go:18)   XCHGL   AX, AX
    0x0068 00104 (test.go:18)   CALL    runtime.deferreturn(SB)
    0x006d 00109 (test.go:18)   MOVQ    32(SP), BP
    0x0072 00114 (test.go:18)   ADDQ    $40, SP
    0x0076 00118 (test.go:18)   RET
    0x0077 00119 (test.go:12)   XCHGL   AX, AX
    0x0078 00120 (test.go:12)   CALL    runtime.deferreturn(SB)
    0x007d 00125 (test.go:12)   MOVQ    32(SP), BP
    0x0082 00130 (test.go:12)   ADDQ    $40, SP
    0x0086 00134 (test.go:12)   RET
    0x0087 00135 (test.go:12)   NOP
    0x0087 00135 (test.go:10)   PCDATA  $0, $-1
    0x0087 00135 (test.go:10)   PCDATA  $2, $-1
    0x0087 00135 (test.go:10)   CALL    runtime.morestack_noctxt(SB)
    0x008c 00140 (test.go:10)   JMP 0
    ...
    0x0000 00000 (test.go:21)   TEXT    "".f2(SB), ABIInternal, $32-8
    0x0000 00000 (test.go:21)   MOVQ    (TLS), CX
    0x0009 00009 (test.go:21)   CMPQ    SP, 16(CX)
    0x000d 00013 (test.go:21)   JLS 117
    0x000f 00015 (test.go:21)   SUBQ    $32, SP
    0x0013 00019 (test.go:21)   MOVQ    BP, 24(SP)
    0x0018 00024 (test.go:21)   LEAQ    24(SP), BP
    0x001d 00029 (test.go:21)   FUNCDATA    $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x001d 00029 (test.go:21)   FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x001d 00029 (test.go:21)   FUNCDATA    $3, gclocals·9fb7f0986f647f17cb53dda1484e0f7a(SB)
    0x001d 00029 (test.go:21)   PCDATA  $2, $0
    0x001d 00029 (test.go:21)   PCDATA  $0, $0
    0x001d 00029 (test.go:21)   MOVQ    $0, "".i+40(SP)
    0x0026 00038 (test.go:22)   MOVL    $8, (SP)
    0x002d 00045 (test.go:22)   PCDATA  $2, $1
    0x002d 00045 (test.go:22)   LEAQ    "".f2.func1·f(SB), AX
    0x0034 00052 (test.go:22)   PCDATA  $2, $0
    0x0034 00052 (test.go:22)   MOVQ    AX, 8(SP)
    0x0039 00057 (test.go:22)   PCDATA  $2, $1
    0x0039 00057 (test.go:22)   LEAQ    "".i+40(SP), AX
    0x003e 00062 (test.go:22)   PCDATA  $2, $0
    0x003e 00062 (test.go:22)   MOVQ    AX, 16(SP)
    0x0043 00067 (test.go:22)   CALL    runtime.deferproc(SB)
    0x0048 00072 (test.go:22)   TESTL   AX, AX
    0x004a 00074 (test.go:22)   JNE 101
    0x004c 00076 (test.go:26)   MOVQ    $1000, "".i+40(SP)
    0x0055 00085 (test.go:27)   XCHGL   AX, AX
    0x0056 00086 (test.go:27)   CALL    runtime.deferreturn(SB)
    0x005b 00091 (test.go:27)   MOVQ    24(SP), BP
    0x0060 00096 (test.go:27)   ADDQ    $32, SP
    0x0064 00100 (test.go:27)   RET
    0x0065 00101 (test.go:22)   XCHGL   AX, AX
    0x0066 00102 (test.go:22)   CALL    runtime.deferreturn(SB)
    0x006b 00107 (test.go:22)   MOVQ    24(SP), BP
    0x0070 00112 (test.go:22)   ADDQ    $32, SP
    0x0074 00116 (test.go:22)   RET
    0x0075 00117 (test.go:22)   NOP
    0x0075 00117 (test.go:21)   PCDATA  $0, $-1
    0x0075 00117 (test.go:21)   PCDATA  $2, $-1
    0x0075 00117 (test.go:21)   CALL    runtime.morestack_noctxt(SB)
    0x007a 00122 (test.go:21)   JMP 0
    ...                    ........
    rel 16+8 t=1 type.[2]interface {}+0 

感觉离真相只差一步了,就是看完这段汇编代码就能搞明白这个return在无名和有名返回值时分别做了什么,所谓的零时变量是咋分配的,想想就有点小激动呢

但是,比较棘手的是,我没学过汇编-_-!

但是again,这有什么关系呢,两个函数既然执行结果不一样,那么在汇编层面肯定也有不一样的地方,于是开始找不同,最终在上面的汇编代码分别找到关键信息如下

"".f2 STEXT size=124 args=0x8 locals=0x20
    0x0000 00000 (test.go:21)   TEXT    "".f2(SB), ABIInternal, $32-8
    0x0000 00000 (test.go:21)   MOVQ    (TLS), CX
    0x0009 00009 (test.go:21)   CMPQ    SP, 16(CX)
    0x000d 00013 (test.go:21)   JLS 117
    0x000f 00015 (test.go:21)   SUBQ    $32, SP
    0x0013 00019 (test.go:21)   MOVQ    BP, 24(SP)
    0x0018 00024 (test.go:21)   LEAQ    24(SP), BP
    0x001d 00029 (test.go:21)   FUNCDATA    $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x001d 00029 (test.go:21)   FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x001d 00029 (test.go:21)   FUNCDATA    $3, gclocals·9fb7f0986f647f17cb53dda1484e0f7a(SB)
    0x001d 00029 (test.go:21)   PCDATA  $2, $0
    0x001d 00029 (test.go:21)   PCDATA  $0, $0
    0x001d 00029 (test.go:21)   MOVQ    $0, "".i+40(SP)
    0x0026 00038 (test.go:22)   MOVL    $8, (SP)
    0x002d 00045 (test.go:22)   PCDATA  $2, $1
    0x002d 00045 (test.go:22)   LEAQ    "".f2.func1·f(SB), AX
    0x0034 00052 (test.go:22)   PCDATA  $2, $0
    0x0034 00052 (test.go:22)   MOVQ    AX, 8(SP)
    0x0039 00057 (test.go:22)   PCDATA  $2, $1
    0x0039 00057 (test.go:22)   LEAQ    "".i+40(SP), AX
    0x003e 00062 (test.go:22)   PCDATA  $2, $0
    0x003e 00062 (test.go:22)   MOVQ    AX, 16(SP)
    0x0043 00067 (test.go:22)   CALL    runtime.deferproc(SB)
    0x0048 00072 (test.go:22)   TESTL   AX, AX
    0x004a 00074 (test.go:22)   JNE 101
    0x004c 00076 (test.go:26)   MOVQ    $1000, "".i+40(SP)
    0x0055 00085 (test.go:27)   XCHGL   AX, AX
    0x0056 00086 (test.go:27)   CALL    runtime.deferreturn(SB)
    0x005b 00091 (test.go:27)   MOVQ    24(SP), BP
    0x0060 00096 (test.go:27)   ADDQ    $32, SP
    0x0064 00100 (test.go:27)   RET
    0x0065 00101 (test.go:22)   XCHGL   AX, AX
    0x0066 00102 (test.go:22)   CALL    runtime.deferreturn(SB)
    0x006b 00107 (test.go:22)   MOVQ    24(SP), BP
    0x0070 00112 (test.go:22)   ADDQ    $32, SP
    0x0074 00116 (test.go:22)   RET
    0x0075 00117 (test.go:22)   NOP
    0x0075 00117 (test.go:21)   PCDATA  $0, $-1
    0x0075 00117 (test.go:21)   PCDATA  $2, $-1
    0x0075 00117 (test.go:21)   CALL    runtime.morestack_noctxt(SB)
    0x007a 00122 (test.go:21)   JMP 0 

这是f2有名返回值的关键信息,主要看

 0x004c 00076 (test.go:26)   MOVQ    $1000, "".i+40(SP) 

这个大概意思就是把1000放到"".i+40(SP)这个内存地址上,然后下面执行的操作就是返回了

"".f1 STEXT size=145 args=0x8 locals=0x28
    0x0000 00000 (test.go:10)   TEXT    "".f1(SB), ABIInternal, $40-8
    0x0000 00000 (test.go:10)   MOVQ    (TLS), CX
    0x0009 00009 (test.go:10)   CMPQ    SP, 16(CX)
    0x000d 00013 (test.go:10)   JLS 135
    0x000f 00015 (test.go:10)   SUBQ    $40, SP
    0x0013 00019 (test.go:10)   MOVQ    BP, 32(SP)
    0x0018 00024 (test.go:10)   LEAQ    32(SP), BP
    0x001d 00029 (test.go:10)   FUNCDATA    $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x001d 00029 (test.go:10)   FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x001d 00029 (test.go:10)   FUNCDATA    $3, gclocals·9fb7f0986f647f17cb53dda1484e0f7a(SB)
    0x001d 00029 (test.go:10)   PCDATA  $2, $0
    0x001d 00029 (test.go:10)   PCDATA  $0, $0
    0x001d 00029 (test.go:10)   MOVQ    $0, "".~r0+48(SP)
    0x0026 00038 (test.go:11)   MOVQ    $0, "".i+24(SP)
    0x002f 00047 (test.go:12)   MOVL    $8, (SP)
    0x0036 00054 (test.go:12)   PCDATA  $2, $1
    0x0036 00054 (test.go:12)   LEAQ    "".f1.func1·f(SB), AX
    0x003d 00061 (test.go:12)   PCDATA  $2, $0
    0x003d 00061 (test.go:12)   MOVQ    AX, 8(SP)
    0x0042 00066 (test.go:12)   PCDATA  $2, $1
    0x0042 00066 (test.go:12)   LEAQ    "".i+24(SP), AX
    0x0047 00071 (test.go:12)   PCDATA  $2, $0
    0x0047 00071 (test.go:12)   MOVQ    AX, 16(SP)
    0x004c 00076 (test.go:12)   CALL    runtime.deferproc(SB)
    0x0051 00081 (test.go:12)   TESTL   AX, AX
    0x0053 00083 (test.go:12)   JNE 119
    0x0055 00085 (test.go:17)   MOVQ    $1000, "".i+24(SP)
    0x005e 00094 (test.go:18)   MOVQ    $1000, "".~r0+48(SP)
    0x0067 00103 (test.go:18)   XCHGL   AX, AX
    0x0068 00104 (test.go:18)   CALL    runtime.deferreturn(SB)
    0x006d 00109 (test.go:18)   MOVQ    32(SP), BP
    0x0072 00114 (test.go:18)   ADDQ    $40, SP
    0x0076 00118 (test.go:18)   RET
    0x0077 00119 (test.go:12)   XCHGL   AX, AX
    0x0078 00120 (test.go:12)   CALL    runtime.deferreturn(SB)
    0x007d 00125 (test.go:12)   MOVQ    32(SP), BP
    0x0082 00130 (test.go:12)   ADDQ    $40, SP
    0x0086 00134 (test.go:12)   RET
    0x0087 00135 (test.go:12)   NOP
    0x0087 00135 (test.go:10)   PCDATA  $0, $-1
    0x0087 00135 (test.go:10)   PCDATA  $2, $-1
    0x0087 00135 (test.go:10)   CALL    runtime.morestack_noctxt(SB)
    0x008c 00140 (test.go:10)   JMP 0 

这是f1无名返回值的关键信息,主要看

 0x0055 00085 (test.go:17)   MOVQ    $1000, "".i+24(SP)
    0x005e 00094 (test.go:18)   MOVQ    $1000, "".~r0+48(SP) 

这个大概意思就是把1000放到"".i+24(SP)这个内存地址上,然后又把1000赋给了"".r0+48(SP),这就是和f1不一样的地方。对应前面结论,我们在这里找到了验证。大致过程就是无名返回值的情况,在return的时候开辟了一个新内存空间,后续的defer读取的还是"".i+24(SP)这样的内存地址而无法读取临时空间的值。return在函数最后返回的也是"".r0+48(SP)对应的值即1000。(因为没有研究过汇编,有些细节可能有待考证)

结论

到此,我们算是搞明白了Go语言里面return和defer之间的微妙关系,从汇编层面看清了在无名返回值和有名返回值return返回的差异。

收藏
评论区

相关推荐

Golang高阶:Golang1.5到Golang1.12包管理
版权所有,转载请注明:http://www.lenggirl.com/go/gomod.html(https://links.jianshu.com/go?tohttp%3A%2F%2Fwww.lenggirl.com%2Fgo%2Fgomod.html) 1. 前言 Golang 是一门到如今有十年的静态高级语言了,2009年的时
godoc 命令和 golang 代码文档管理
介绍 godoc 是 golang 自带的文档查看器,更多的提供部署服务 go doc 和 godoc 在 golang 1.13 被移除了,可以自行安装 golang.org go1.13 godoc(https://links.jianshu.com/go?tohttps%3A%2F%2Fgolang.org%2Fdoc%2Fg
为什么GOPROXY对Golang开发如此重要
为什么GOPROXY对Golang开发如此重要 引言 从Go 1.13开始,Go Module作为Golang中的标准包管理器,在安装时自动启用,并附带一个默认的GOPROXY。 但是对于其他的GOPROXY选项,比如JFrog GoCenter,以及你自己的Go Module包,你需要在公众视野中保持安全,你应该选择什么样的配置? 你怎样才能
Go语言学习——彻底弄懂return和defer的微妙关系
疑问前面在函数篇里介绍了Go语言的函数是支持多返回值的。只要在函数体内,对返回值赋值,最后加上return就可以返回所有的返回值。最近在写代码的时候经常遇到在return后,还要在defer里面做一些收尾工作,比如事务的提交或回滚。所以想弄清楚这个return和defer到底是什么关系,它们谁先谁后,对于最后返回值又有什么影响呢? 动手验证了解
Go 语言编程 — go mod 依赖包管理
目录 == ### 文章目录 * 目录 * go mod 依赖包管理 * 使用 go mod go mod 依赖包管理 ============ go mod 是 Golang 1.11 版本引入的依赖包管理工具。其中,Golang 对 Modules 的定义:Modules 是相关 Go Packages 的集合,是源代码交换和版本控制
Go初识
Go初识 ---- 下载安装包:[https://golang.org/dl/](https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgolang.org%2Fdl%2F) ### 什么是Go语言 * Go语言也称为 Golang,是由 Google 公司开发的一种静态强类型、编译型、
Go语言基础之并发
转载自[https://www.liwenzhou.com/posts/Go/14\_concurrence/](https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.liwenzhou.com%2Fposts%2FGo%2F14_concurrence%2F) Go语言基础之并发 ==
go mod 无法自动下载依赖包的问题
go 11以后启用了go mod功能,用于管理依赖包。 当执行`go mod init`生成`go.mod`文件之后,golang在`运行`、`编译`项目的时候,都会检查依赖并下载依赖包。 在启动了`go mod`之后,通过`go mod`下载的依赖包,不在放在`GOPATH/src`中,而是放到`GOPATH/pkg/mod`中。 比如我当前的`GO
go系列(3)
这篇讲讲如何在beego框架使用redis。 golang中比较好用的第三方开源redisclient有: * go-redis * 源码地址:[https://github.com/go-redis/redis](https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgith
vs code 下安装golang支持
1)安装gocode go get -u -v github.com/nsf/gocode 2)安装godef go get -u -v github.com/rogpeppe/godef 3)安装golint go get -u -v github.com/golang/lint/golint 4)安装go-find-references g
Archlinux下Visual Studio Code配置Golang开发环境
一、Golang的安装 ----------- GoLang安装并验证一下: [cox@localhost ~]$ sudo pacman -S go [cox@localhost ~]$ go version go version go1.8.3 linux/amd64s 要注意,Golang的安装要确保两个环境变量,一个是G
Golang 开发环境搭建
Golang 是 Google 发布的开发语言,Go 编译的程序速度可以媲美 C/C++。 安装 -- sudo apt-get install golang sudo apt-get install golang-go.tools 使用 -- * 编译运行程序 go run main.go * 查看命令文
Golang1.7 Goroutine源码分析(转)
一、       Golang简介 **1.1概述** Golang语言是Google公司开发的新一代编程语言,简称Go语言,Go 是有表达力、简洁、清晰和有效率的。它的并行机制使其很容易编写多核和网络应用,而新奇的类型系统允许构建有弹性的模块化程序。 Go 编译到机器码非常快速,同时具有便利的垃圾回收和强大的运行时反射。而他最广为人知的特性便是语言层面
Golang中defer、return、返回值之间执行顺序的坑
原文链接:https://studygolang.com/articles/4809 Go语言中延迟函数defer充当着 cry...catch 的重任,使用起来也非常简便,然而在实际应用中,很多gopher并没有真正搞明白defer、return和返回值之间的执行顺序,从而掉进坑中,今天我们就来揭开它的神秘面纱! 先来运行下面两段代码: A. 无名返
Golang代码测试:一点到面用测试驱动开发
> **摘要**:TDD(Test Driven Development),测试驱动开发。期望局部最优到全局最优,这个是一种非常不错的好习惯。 了解Golang的测试之前,我们先了解一下go语言自带的测试工具。 go test工具 --------- Go语言中的测试依赖go test命令。编写测试代码和编写普通的Go代码过程是类似的,并不需要学习新的