作者:林冠宏 / 指尖下的幽灵

掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8

博客:http://www.cnblogs.com/linguanh/

GitHub : https://github.com/af913337456/

腾讯云专栏: https://cloud.tencent.com/developer/user/1148436/activities

虫洞区块链专栏:https://www.chongdongshequ.com/article/1536563643883.html


前序

deferGo语言中一个很重要的关键词。本文主要以简短的手法列举出,它在不同的多种常见代码片段中,所体现出来的不一样的效果。从笔试的角度来看,可以说是覆盖了绝大部分题型。

此外,在本文之前,还有本人另一篇同样使用例子的形式channel 数据类型做直观讲解的文章。

Golang, 以17个简短代码片段,切底弄懂 channel 基础

目录

  • defer 的主要特点
  • 非引用传参给defer调用的函数,且为非闭包函数情况
  • 传递引用给defer调用的函数,即使不使用闭包函数情况
  • 传递值给defer调用的函数,且非闭包函数情况
  • defer调用闭包函数,且内调用外部非传参进来的变量的情况
  • defer调用闭包函数,若内部使用了传参参数的值的情况
  • defer所调用的非闭包函数,参数如果是函数的情况
  • defer 不影响 return的值
  • 闭包函数对 defer 的影响

defer 的主要特点

  • 延迟调用
  • 所在的函数中,它在 returnpanic执行完毕 后被调用
  • 多个 defer,它们的被调用顺序,为的形式。先进后出,先定义的后被调用
func Test_1(t *testing.T) {
// defer 的调用顺序。由下到上,为 栈的形式。先进后出
defer0() // ↑
defer1() // |
defer2() // |
defer3() // |
//defer4() // |
defer5() // |
defer6() // |
defer7() // |
defer8() // | 从下往上
}

非引用传参给defer调用的函数,且为非闭包函数,值不会受后面的改变影响

func defer0() {
a := 3 // a 作为演示的参数
defer fmt.Println(a) // 非引用传参,非闭包函数中,a 的值 不会 受后面的改变影响
a = a + 2
}
// 控制台输出 3

传递引用给defer调用的函数,即使不使用闭包函数,值也受后面的改变影响

func myPrintln(point *int)  {
fmt.Println(*point) // 输出引用所指向的值
}
func defer1() {
a := 3
// &a 是 a 的引用。内存中的形式: 0x .... ---> 3
defer myPrintln(&a) // 传递引用给函数,即使不使用闭包函数,值 会 受后面的改变影响
a = a + 2
}
// 控制台输出 5

传递值给defer调用的函数,且非闭包函数,值不会受后面的改变影响

func p(a int)  {
fmt.Println(a)
} func defer2() {
a := 3
defer p(a) // 传递值给函数,且非闭包函数,值 不会 受后面的改变影响
a = a + 2
}
// 控制台输出: 3

defer调用闭包函数,且内调用外部非传参进来的变量,值受后面的改变影响

// 闭包函数内,事实是该值的引用
func defer3() {
a := 3
defer func() {
fmt.Println(a) // 闭包函数内调用外部非传参进来的变量,事实是该值的引用,值 会 受后面的改变影响
}()
a = a + 2 // 3 + 2 = 5
}
// 控制台输出: 5
// defer4 会抛出数组越界错误。
func defer4() {
a := []int{1,2,3}
for i:=0;i<len(a);i++ {
// 同 defer3 的闭包形式。因为 i 是外部变量,没用通过传参的形式调用。在闭包内,是引用。
// 值 会 受 ++ 改变影响。导致最终 i 是3, a[3] 越界
defer func() {
fmt.Println(a[i])
}()
}
}
// 结果:数组越界错误

defer调用闭包函数,若内部使用了传参参数的值。使用的是值

func defer5() {
a := []int{1,2,3}
for i:=0;i<len(a);i++ {
// 闭包函数内部使用传参参数的值。内部的值为传参的值。
defer func(index int) {
fmt.Println(a[index]) // index == i
}(i)
// 后进先出,3 2 1
}
}
// 控制台输出:
// 3
// 2
// 1

defer所调用的非闭包函数,参数如果是函数,会按顺序先执行(函数参数)

func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func defer6() {
a := 1
b := 2
// calc 充当了函数中的函数参数。即使在 defer 的函数中,它作为函数参数,定义的时候也会首先调用函数进行求值
// 按照正常的顺序,calc("10", a, b) 首先被调用求值。calc("122", a, b) 排第二被调用
defer calc("1", a, calc("10", a, b))
defer calc("12",a, calc("122", a, b))
}
// 控制台输出:
/**
10 1 2 3 // 第一个函数参数
122 1 2 3 // 第二个函数参数
12 1 3 4 // 倒数第一个 calc
1 1 3 4 // 倒数第二个 calc
*/

defer 不影响 return的值

下面两个例子的结论是:

  • 无论 defer 内部调用传递的是值还是引用。都不会改变 return 的返回结果。返回值的确定,比 defer 早
func defer7() int {
a := 2
defer func() {
a = a + 2
}()
return a
}
// 控制台输出:2
func add(i *int)  {
*i = *i + 2
} func defer8() int {
a := 2
defer add(&a)
return a
}
// 控制台输出:2

原理:

    例如:return a,此行代码经过编译后,会被拆分为:
1. 返回值 = a
2. 调用 defer 函数
3. return

闭包函数对 defer 的影响

函数中,值传递引用传递它们的区别是比较简单的,为基础的 C 语言指针知识。

而对于为什么 defer 修饰的背包函数,如果函数内部不是使用传参的参数时,它所能起到的引用修改作用。原理如下:

a := 2
func() {
fmt.Println(a)
}()
a = a + 3
// 内存
闭包外:
1. a 实例化
2. a地址 ---> 2
闭包内:
1. a 地址被传递进来
2. a地址 ---> 2
3. a = a + 3
4. 输出 5

Golang, 以 9 个简短代码片段,弄懂 defer 的使用特点的更多相关文章

  1. Golang, 以17个简短代码片段,切底弄懂 channel 基础

    (原创出处为本博客:http://www.cnblogs.com/linguanh/) 前序: 因为打算自己搞个基于Golang的IM服务器,所以复习了下之前一直没怎么使用的协程.管道等高并发编程知识 ...

  2. [Go] 通过 17 个简短代码片段,切底弄懂 channel 基础

    关于管道 Channel Channel 用来同步并发执行的函数并提供它们某种传值交流的机制. Channel 的一些特性:通过 channel 传递的元素类型.容器(或缓冲区)和 传递的方向由“&l ...

  3. Android适配器之ArrayAdapter、SimpleAdapter和BaseAdapter的简单用法与有用代码片段(转)

    摘自:http://blog.csdn.net/shakespeare001/article/details/7926783 Adapter是连接后端数据和前端显示的适配器接口,是数据Data和UI( ...

  4. [转]Android适配器之ArrayAdapter、SimpleAdapter和BaseAdapter的简单用法与有用代码片段

      收藏ArrayAdapter.SimpleAdapter和BaseAdapter的一些简短代码片段,希望用时方便想起其用法. 1.ArrayAdapter 只可以简单的显示一行文本 代码片段: A ...

  5. golang代码片段(摘抄)

    以下是从golang并发编程实战2中摘抄过来的代码片段,主要是实现一个简单的tcp socket通讯(客户端发送一个数字,服务端计算该数字的立方根然后返回),写的不错,用到了go的并发以及看下郝林大神 ...

  6. python超实用的30 个简短的代码片段(三)

    Python是目前最流行的语言之一,它在数据科学.机器学习.web开发.脚本编写.自动化方面被许多人广泛使用. 它的简单和易用性造就了它如此流行的原因. 如果你正在阅读本文,那么你或多或少已经使用过P ...

  7. python超实用的30 个简短的代码片段(二)

    Python是目前最流行的语言之一,它在数据科学.机器学习.web开发.脚本编写.自动化方面被许多人广泛使用. 它的简单和易用性造就了它如此流行的原因. 如果你正在阅读本文,那么你或多或少已经使用过P ...

  8. jQuery Mobile高手必备的十大技巧和代码片段

    与任何新技术一样,常常难就难在如何开始入手. 有鉴于此,我们整理出了与jQuery Mobile库有关的我认为最便利的一些技巧.方法和代码片段. 由于本文不是旨在全面介绍使用jQuery Mobile ...

  9. VSCode 如何操作用户自定义代码片段

    自己写了一些根据自己习惯弄成的自定义代码片段,不喜跳过 很简单,快速过一下,F1,然后输入 snippets vue代码片段 { // Place your snippets for vue here ...

随机推荐

  1. python之类和对象

    对象(object)基本上可以看做数据(特性)以及由一系列可以存取.操作这些数据的方法所组成的集合. 类,可以看成种类,类型,从一组对象中提取到的相似部分.所有的对象都属于一个类,称为类的实例. 之前 ...

  2. C/JS_二分法查找

    1. 二分法查找 前提: 数据是排好序的. 题设:给出一个有序arr,从中找出key,arr的区间是array[ low , higt]. 步骤: (1)mid=(low+high)/2 (2)arr ...

  3. jQusery .基础

    1.jQusery 的基本用法 <%@ page language="java" contentType="text/html; charset=UTF-8&quo ...

  4. GMA Round 1 数列求单项

    传送门 数列求单项 在数列{$a_n$}中,$a_1=-\frac{1}{4}$,$\frac{1}{a_{n+1}}+\frac{1}{a_n}=\begin{cases}-3(n为偶数)\\3(n ...

  5. MYSQL的联合查询最好是少用,效能差异巨大

    同样的功能,不同的写法,时间和内存占用差了几千倍,不废话,直接上代码 第一种写法: 代码如下: $Rs=DB::get($_ENV['DB'],3,"SELECT * FROM _xiazh ...

  6. [转]jQuery中clone和clone(true)的区别

    jquery中clone() 和 clone(true)的区别. jquery复制 DOM的时候,原来还可以连 dom上面绑定的事件一起复制. 原文: https://www.cnblogs.com/ ...

  7. IDEA攻略合辑

    AS使用lombok注解报错:Annotation processors must be explicitly declared now. The following dependencies on ...

  8. C#读取Excel文件的简单方法

    一.简述 本文讲C#通过第三方库读取Excel的最简单的方法,下文给一个读取行数的例子. 二.依赖 引入nuget.org包如下: <?xml version="1.0" e ...

  9. Linux中查看文件夹占用磁盘大小

    一.命令 ./ du -h ./ 查看当前目录占用空间 二.样例

  10. Mac NPM 配置

    1.NPM 简介 NPM(node package manager),通常称为 node 包管理器,是目前世界上最大的开源库生态系统.使用 NPM 可以对 node 包进行安装.卸载.更新.查看.搜索 ...