解锁即可观看《Go开发工程师》完整课程视频

Go开发工程师

未来3-5年企业高性能项目不可替代的语言,从基础到项目实战再到重构,真正从入门到精通

【第1周】Go基础知识入门
【第2周】容器,go编程思想
【第3周】Go并发编程和工程管理
【第4周】从0开始理解rpc和grpc
【第5周】grpc和protobuf进阶
【第6周】 yapi文档管理、gorm详解
【第7周】gin快速入门
【第8周】用户服务的grpc服务
【第9周】用户服务的web服务
【第10周】服务注册/发现、配置中心、负载均衡
【第11周】商品微服务的grpc服务
【第12周】 商品微服务的gin层和oss图片服务
【第13周】库存服务和分布式锁
【第14周】订单和购物车微服务
【第15周】 支付宝支付、用户操作微服务、前后端联调
【第16周】elasticsearch实现搜索微服务
【第17周】 分布式理论基础、分布式事务解决方案
【第18周】 学习rocketmq实现幂等性机制等
【第19周】链路追踪、限流、熔断、降级
【第20周】api网关、部署
【第21周】开发规范和go基础扩展
【第22周】设计模式和单元测试
【第23周】protoc插件开发、cobra命令行
【第24周】log日志包设计
【第25周】ast代码生成工具开发
【第26周】三层代码结构
【第27周】grpc服务封装更方便的rpc服务
【第28周】深入grpc的服务注册、负载均衡原理
【第29周】基于gin封装api服务
【第30周】可观测的终极解决方案
【第31周】系统监控核心
【第32周】用户、商品服务重构
【第33周】订单、库存等服务重构
【第33+周】订单服务重构、wire进行ioc控制
【第34周】通过k8s部署服务
【第34+周】devops和k8s
章节
问答
课签
笔记
评论
占位
占位

Go语言-defer语句

    与select语句一样,Go语言中的defer语句也非常独特,而且比前者有过之而无不及。defer语句仅能被放置在函数或方法中。它由关键字defer和一个调用表达式组成。注意,这里的调用表达式所表示的既不能是对Go语言内建函数的调用也不能是对Go语言标准库代码包unsafe中的那些函数的调用。实际上,满足上述条件的调用表达式被称为表达式语句。请看下面的示例:

func readFile(path string) ([]byte, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close()
    return ioutil.ReadAll(file)
}

    函数readFile的功能是读出指定文件或目录(以下统称为文件)本身的内容并将其返回,同时当有错误发生时立即向调用方报告。其中,osioutil(导入路径是io/ioutil)代表的都是Go语言标准库中的代码包。请注意这个函数中的倒数第二条语句。我们在打开指定文件且未发现有错误发生之后,紧跟了一条defer语句。其中携带的表达式语句表示的是对被打开文件的关闭操作。注意,当这条defer语句被执行的时候,其中的这条表达式语句并不会被立即执行。它的确切的执行时机是在其所属的函数(这里是readFile)的执行即将结束的那个时刻。也就是说,在readFile函数真正结束执行的前一刻,file.Close()才会被执行。这也是defer语句被如此命名的原因。我们在结合上下文之后就可以看出,语句defer file.Close()的含义是在打开文件并读取其内容后及时地关闭它。该语句可以保证在readFile函数将结果返回给调用方之前,那个文件或目录一定会被关闭。这实际上是一种非常便捷和有效的保险措施。
   
    更为关键的是,无论readFile函数正常地返回了结果还是由于在其执行期间有运行时恐慌发生而被剥夺了流程控制权,其中的file.Close()都会在该函数即将退出那一刻被执行。这就更进一步地保证了资源的及时释放。
   
    注意,当一个函数中存在多个defer语句时,它们携带的表达式语句的执行顺序一定是它们的出现顺序的倒序。下面的示例可以很好的证明这一点:

func deferIt() {
    defer func() {
        fmt.Print(1)
    }()
    defer func() {
        fmt.Print(2)
    }()
    defer func() {
        fmt.Print(3)
    }()
    fmt.Print(4)
}

    deferIt函数的执行会使标准输出上打印出4321。请大家猜测下面这个函数被执行时向标准输出打印的内容,并真正执行它以验证自己的猜测。最后论证一下自己的猜测为什么是对或者错的。

func deferIt2() {
    for i := 1; i < 5; i++ {
        defer fmt.Print(i)
    }
}

    最后,对于defer语句,我还有两个特别提示:
   
    1. defer携带的表达式语句代表的是对某个函数或方法的调用。这个调用可能会有参数传入,比如:fmt.Print(i + 1)。如果代表传入参数的是一个表达式,那么在defer语句被执行的时候该表达式就会被求值了。注意,这与被携带的表达式语句的执行时机是不同的。请揣测下面这段代码的执行:

func deferIt3() {
    f := func(i int) int {
        fmt.Printf("%d ",i)
        return i * 10
    }
    for i := 1; i < 5; i++ {
        defer fmt.Printf("%d ", f(i))
    }
}

    它在被执行之后,标准输出上打印出1 2 3 4 40 30 20 10
   
    2. 如果defer携带的表达式语句代表的是对匿名函数的调用,那么我们就一定要非常警惕。请看下面的示例:

func deferIt4() {
    for i := 1; i < 5; i++ {
        defer func() {
            fmt.Print(i)
        }()
    }
}     

    deferIt4函数在被执行之后标出输出上会出现5555,而不是4321。原因是defer语句携带的表达式语句中的那个匿名函数包含了对外部(确切地说,是该defer语句之外)的变量的使用。注意,等到这个匿名函数要被执行(且会被执行4次)的时候,包含该defer语句的那条for语句已经执行完毕了。此时的变量i的值已经变为了5。因此该匿名函数中的打印函数只会打印出5。正确的用法是:把要使用的外部变量作为参数传入到匿名函数中。修正后的deferIt4函数如下:

func deferIt4() {
    for i := 1; i < 5; i++ {
        defer func(n int) {
            fmt.Print(n)
        }(i)
    }
}

    请大家自行验证一下它的正确性。

任务

    命令源码文件index.go中代码的功能是打印出斐波那契数列的前10个数,即:0 1 1 2 3 5 8 13 21 34 。请把第 行的fmt.Printf函数调用语句修改成一条defer语句,使得该文件被执行之后标准输出上会出现:

0 1 1 2 3 5 8 13 21 34 34 21 13 8 5 3 2 1 1 0  

    显然,这是关于斐波那契数列的一段回文。提示一下,这需要利用前面讲到的defer语句的几个特殊行为。尤其是最后的“特别提示”中讲到的一些内容。

?不会了怎么办

第9行的语句应该被修改成:

defer func(n int) {
    fmt.Printf("%d ", n)
}(func() int {
    n := fibonacci(i)
    fmt.Printf("%d ", n)
    return n
}())
||

提问题

写笔记

公开笔记
提交
||

请验证,完成请求

由于请求次数过多,请先验证,完成再次请求

加群二维码

打开微信扫码自动绑定

您还未绑定服务号

绑定后可得到

  • · 粉丝专属优惠福利
  • · 大咖直播交流干货
  • · 课程更新,问题答复提醒
  • · 账号支付安全提醒

收藏课程后,能更快找到我哦~

使用 Ctrl+D 可将课程添加到书签

邀请您关注公众号
关注后,及时获悉本课程动态

举报

0/150
提交
取消
全部 精华 我要发布
全部 我要发布
最热 最新
只看我的

手记推荐

更多

本次提问将花费2个积分

你的积分不足,无法发表

为什么扣积分?

本次提问将花费2个积分

继续发表请点击 "确定"

为什么扣积分?