Go基础编程 - 15 - 延迟调用(defer)

news2024/9/27 10:47:59

延迟调用 defer

    • 1. 特性
    • 2. 常用用途
    • 3. defer 执行顺序:`同函数内`先进后出
    • 4. defer 闭包
    • 5. defer 陷阱


上一篇:泛型


1. 特性

1. 关键字 defer 用于注册延迟调用。
2. defer 调用直到 return 前才被执行。
3. 同函数内多个 defer 语句,按先进后出的方式执行。
4. defer 语句中的变量,在 defer 声明时就决定了。

2. 常用用途

滥用 defer 可能会造成性能问题,尤其是在一个“大循环”里。

1. 关闭文件句柄。
2. 锁资源释放。
3. 数据库连接释放。

3. defer 执行顺序:同函数内先进后出

package main

import "fmt"

func defer1(i int) {
	defer fmt.Printf("defer1 %d - 1\n", i)
	defer fmt.Printf("defer1 %d - 2\n", i)
}

func main() {
	// 函数内 defer,在函数执行结束前执行。
	// 因此嵌套函数 defer1() 内的 defer 在自身函数内按先进后出的顺序执行。
	// 且 defer1() 内的 defer 在 main 函数内的执行顺序仅取决于 defer1() 函数的执行顺序。
	defer defer1(100)
	defer1(200)		 

	defer fmt.Println("main 1")
	
	defer1(300)			
	defer defer1(400)	
}

以上代码执行顺序及输出结果如下图:

  1. 函数内 defer,在函数执行结束前执行。
  2. main 函数内,关键字 defer 在 main 函数内按先进后出顺序在 main 函数执行结束前执行,其它代码按顺序执行。
  3. defer1() 函数内关键字 defer 在 defer1 函数内按先进后出顺序执行。
  4. defer1() 函数内的输出在自身函数执行结束前全部输出。
    在这里插入图片描述

多个 defer 注册,按 FILO 次序执行 ( 先进后出 )。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行。

func main() {
	defer println(1)
	defer println(2)

	i := 0
	defer func() {
		println(100 / i)	// 此处抛出 panic,但下面 defer 依然执行
	}()

	defer println(3)
}

输出结果

3
2
1
panic: runtime error: integer divide by zero

4. defer 闭包

defer 语句适用于单个函数或语句的延迟执行。当需要执行更复杂的逻辑或者多个操作时,可以使用 defer func () 闭包。

  • 每次执行 defer 语句时,函数值调用的参数都会进行求值并保存,但不执行实际的函数。 也就是复制了一份。

    func main() {
    	i := 1
    	defer fmt.Println("one - i =", i) // i 被复制保存
    	
    	// defer 使用了闭包, 此时仅定义了闭包函数,并未执行。
    	defer func() {
    		fmt.Println("two - i =", i)  // 闭包引用 i
    	}()
    
    	// 变量 i 作为函数参数传入时,会进行求值并保存。
    	defer func(n int) {
    		fmt.Println("three - i =", n)
    	}(i) // i 被复制保存
    
    	i = 100
    	
    	defer func(i int) {
    		fmt.Println("four - i =", i)
    	}(i) // i 被复制保存
    
    	fmt.Println("i =", i)
    }
    

    输出结果:

    i = 100
    four - i = 100
    three - i = 1
    two - i = 100
    one - i = 1
    
  • defer 中可以使用指针或闭包“延迟”读取。

    func main() {
    	x, y, z := 10, 10, 10
    	
    	// 延迟读取 y,z 的值
    	defer func(i int, j *int) {
    		fmt.Printf("defer: x = %d, y = %d, z = %d \n", i, *j, z) // y 指针传递,z 闭包引用
    	}(x, &y) // x 被复制
    
    	x += 100
    	y += 100
    	z += 100
    	fmt.Printf("main: x = %d, y = %d, z = %d\n", x, y, z)
    }
    

    输出结果:

    main: x = 110, y = 110, z = 110
    defer: x = 10, y = 110, z = 110
    

5. defer 陷阱

  • defer 与闭包、return

    package main
    
    import (
        "errors"
        "fmt"
        "os"
    )
    
    // 如果 defer 后不是一个闭包,最后执行的时候我们得到的并不是最新的值,而是声明 defer 时保存的值。
    // 有具名返回值函数中,实际值为return前最终计算结果。
    func closure(i, j int) (n int, err error) {
    	defer fmt.Printf("1 n = %d, defer: %v\n", err) // n, err 复制保存当前值
    
    	defer func(err error) {
    		fmt.Printf("2 n = %d, defer: %v\n", err)
    	}(err) // err 保存当前值传参;n 为全局变量
    
    	defer func() {
    		fmt.Printf("3 n = %d, defer: %v\n", err)
    	}() // 闭包引用
    
    	if j == 0 {
    		err = errors.New("divided by zero")
    		return
    	}
    
    	return i / j, nil
    }
    
    // 使用相同的变量释放不同的资源,那么这个操作可能无法正常执行。
    // 1. 示例中打开test.txt、test2.txt都赋值给变量 f。
    // 2. defer 声明使用闭包函数;在执行defer声明的代码时,f 已经被重新赋值为 test2.txt,导致 test.txt 关闭失败。
    //    报错:close failed:test.txt close test2.txt: file already closed
    // 3. 优化建议:避免使用相同变量;或 defer 声明使用值参传参
    func closureCover() {
    	f, err := os.Open("test.txt")
    	if err != nil {
    		fmt.Println("open failed: test.txt")
    	}
    	if f != nil {
    		defer func() {
    			if err := f.Close(); err != nil {
    				fmt.Println("close failed:test.txt", err)
    			}
    		}()
    		// 优化为值传递
    		/*
    		defer func(f *os.File) {
    			...
    		}(f)
    		*/
    	}
    	
    	f, err = os.Open("test2.txt")
    	if err != nil {
    		fmt.Println("open failed: test2.txt")
    	}
    	if f != nil {
    		defer func() {
    			if err := f.Close(); err != nil {
    				fmt.Println("close failed:test2.txt", err)
    			}
    		}() // 优化同上
    	}
    }
    
    
    func main() {
    	closure(2, 0)
    	// 输出:
     	// 3 n = 0, defer: divided by zero
    	// 2 n = 0, defer: <nil>
    	// 1 n = 0, defer: <nil>
    
    	// 有具名返回值函数中,实际值为return前最终计算结果。输出:
    	// 3 n = 5, defer: <nil>
    	// 2 n = 5, defer: <nil>
    	// 1 n = 0, defer: <nil>
    }
    
  • defer nil 函数

    func main() {
    	defer func() {
    		if err := recover(); err != nil {
    			fmt.Println("recover:", err)
    		}
    	}()
    
    	var run func() = nil
    	defer run()
    
    	fmt.Println("running")
    	// 输出:
    	// running
    	// recover: runtime error: invalid memory address or nil pointer dereference
    }
    
  • 在错误位置使用 defer

    package main
    
    import (
    	"fmt"
    	"net/http"
    )
    
    func main() {
    
    	res, err := http.Get("http://www.xxxxxxxxxxx")
    	defer res.Body.Close()
    
    	if err == nil {
    		return
    	}
    	fmt.Println("running")
    	// 输出
    	// panic: runtime error: invalid memory address or nil pointer dereference
    }
    

    当 http.Get 失败时 res 为 nil,我们在 defer 调用 res.Body 时并未判断是否执行成功,会抛出异常。优化如下:

    func main() {
       res, err := http.Get("http://www.xxxxxxxxxxx")
       if err == nil {	 // 判断 http.Get 执行成功时,才需要关闭响应连接
       	defer res.Body.Close()
       }
       fmt.Println("running")
       // 输出
       // running
    }
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2169733.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第17章 协同式任务切换

第17章 协同式任务切换 在多任务系统中&#xff0c;每个任务都有各自的局部描述符表(LDT)和任务状态段(TSS)。 从任务切换的时机来讲&#xff0c;有两种基本的策略&#xff1a; 协同式&#xff1a;从一个任务切换到另一个任务。需要当前任务主动地请求暂时放弃执行权&#x…

好看的首页展示

代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><style>/* RESET…

气膜游泳馆:舒适恒温,寒冷季节中的理想游泳场所—轻空间

随着天气逐渐转凉&#xff0c;许多人在秋冬季节减少了户外活动&#xff0c;尤其是游泳。然而&#xff0c;气膜游泳馆为您提供了一种全新的选择&#xff0c;让您即使在寒冷的季节&#xff0c;也能享受畅游的乐趣。凭借其独特的恒温设计和舒适的环境&#xff0c;气膜游泳馆成为了…

计算机毕业设计宠物领养网站我的发布领养领养用户信息/springboot/javaWEB/J2EE/MYSQL数据库/vue前后分离小程序

目录 1.课题背景 2.课题意义 ‌ 3.技术介绍 4.技术性需求 4.1后端服务‌&#xff1a; 4.2 前端展示‌ 5.数据库设计‌&#xff1a; 6.系统性能‌&#xff1a; 7.安全性‌&#xff1a; 8. 功能介绍&#xff1a; 9. 部分代码 1.课题背景 近年来&#xff0c;随着宠物饲养数量…

TDEngine在煤矿综采管控平台中的应用

一、行业背景 智能综采管控平台&#xff0c;是将煤矿综采工作面传感器数据采集&#xff0c;通过可视化界面展示。实现综采工作面的透明化展示&#xff0c;并基于历史的传感器数据进行机器学习的训练&#xff0c;了解工作面周期来压&#xff0c;设备故障检测等数据应用。因此针…

AIGC引领数智未来:企业架构演进的深度解析与实践路径,The Open Group 2024生态系统架构·可持续发展年度大会专题报道

随着人工智能技术的迅猛发展&#xff0c;特别是以ChatGPT、Sora等为代表的AIGC&#xff08;人工智能生成内容&#xff09;技术的爆发&#xff0c;我们正处于通用人工智能&#xff08;AGI&#xff09;时代的前夜。AIGC技术在多个领域展现出近乎甚至超越人类的能力&#xff0c;已…

【LeetCode】动态规划—最小路径和(附完整Python/C++代码)

动态规划—64. 最小路径和 前言题目描述基本思路1. 问题定义:2. 理解问题和递推关系:3. 解决方法:3.1. 初始化:3.2. 边界条件:3.3. 填充 dp 数组:3.4. 返回结果: 4. 进一步优化:5. 小总结: 代码实现Python3代码实现Python 代码解释C代码实现C 代码解释 总结: 前言 给定一个包含…

liunx系统虚拟机

https://mirrors.aliyun.com/centos/7.9.2009/isos/x86_64/ 下载地址 DVD版本 安装vm软件12通过vm软件来创建一个虚拟机空间通过vm软件在创建好的虚拟机空间上&#xff0c;安装我们的centos操作系统使用centos你得需要将鼠标点击进入界面中&#xff0c;但是鼠标会消失&#xf…

杨辉三角-C语言

1.问题&#xff1a; 输出杨辉三角。 2.解答&#xff1a; 对有特点的数&#xff08;每行开头和结束的数都是1&#xff09;进行赋值&#xff0c;给中间的数进行赋值&#xff0c;把上面赋值后的二维数组&#xff0c;遍历输出。 3.代码&#xff1a; #include<stdio.h>//头…

基于SSM商铺租赁系统JAVA|VUE|SSM计算机毕业设计源代码+数据库+LW文档+开题报告+答辩稿+部署教+代码讲解

源代码数据库LW文档&#xff08;1万字以上&#xff09;开题报告答辩稿 部署教程代码讲解代码时间修改教程 一、开发工具、运行环境、开发技术 开发工具 1、操作系统&#xff1a;Window操作系统 2、开发工具&#xff1a;IntelliJ IDEA或者Eclipse 3、数据库存储&#xff1a…

使用session来实现验证码发送功能

一、什么是session&#xff1f; Session由服务器创建&#xff0c;并为每一次会话分配一个Session对象。同一个浏览器发起的多次请求&#xff0c;同属于一次会话&#xff08;Session&#xff09;。首次使用到Session时&#xff0c;服务器会自动创建Session&#xff0c;并创建Co…

大觅网之自动化部署(Automated Deployment of Da Mi Network)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 本人主要分享计算机核心技…

前端大模型入门:Transformer.js 和 Xenova-引领浏览器端的机器学习变革

除了调用别人的api接口使用transformer技术&#xff0c;你是否想过将大模型在浏览器中运行呢&#xff1f;尤其是WebGPU的出现&#xff0c;性能比WebGL高不少&#xff0c;很多小任务真的不再需要在一个中心运行了。 不少同学买课学python了&#xff0c;但我还是在坚持用js尝试&a…

【Linux实践】实验九:Shell流程控制语句

【Linux实践】实验九&#xff1a;Shell流程控制语句 实验目的实验内容实验步骤及结果1. 变量的定义和使用2. 条件3. 运算4. if 语句5. case 语句6. for 语句7. while 语句8. until 语句9. 遍历复制10. 计算平方 实验目的 1、掌握条件判断语句&#xff0c;如if语句、case语句。…

tomcat的安装,管理与配置

目录 Tomcat 服务部署 1.关闭防火墙&#xff0c;将安装 Tomcat 所需软件包上传到虚拟机 2.安装JDK 3.设置JDK环境变量 4.安装启动Tomcat 5.启动tomcat 6.优化tomcat启动速度 Tomcat 服务管理 systemd 管理控制 supervisor 管理控制 Tomcat 虚拟主机配置 1.创建 sun…

EE trade:黄金 999 和黄金 9999 的区别

黄金&#xff0c; 作为一种珍贵的金属&#xff0c; 一直是人们投资和收藏的对象。 在购买黄金时&#xff0c; 您可能会遇到两种纯度的黄金 —— 黄金 999 和黄金 9999。 这两种黄金有什么区别? 消费者应该如何选择呢? 一、 黄金 999 和黄金 9999 的区别 含金量&#xff1a;…

OCR 行驶证识别 离线识别

目录 正页识别 副页识别 全部识别 OCR 行驶证识别 离线识别 正页识别 副页识别 全部识别

C语言实现归并排序(Merge Sort)

目录 一、递归实现归并排序 1. 归并排序的基本步骤 2.动图演示 3.基本思路 4.代码 二、非递归实现 1.部分代码 2.代码分析 修正后代码&#xff1a; 归并过程打印 性能分析 复杂度分析 归并排序是一种高效的排序算法&#xff0c;采用分治法&#xff08;Divide and Con…

中电金信:“源启”金融级数字底座

01方案简介 金融级数字底座是中电金信依托中国电子自主安全计算产业链&#xff0c;采用新一代技术架构&#xff0c;为金融及重点行业打造的数字化新型基础设施。 “源启”面向金融等重点行业场景&#xff0c;依照系统工程方法论&#xff0c;进行全栈技术产品的验证、适配和调…

word2vector训练数据集整理(代码实现)

import math import os import random import torch import dltools from matplotlib import pyplot as plt #读取数据集 def read_ptb():"""将PTB数据集加载到文本行的列表中"""with open(./ptb/ptb.train.txt) as f:raw_text f.read()return…