本文首发于公众号:Hunter后端

原文链接:Golang基础笔记十二之defer、panic、error

本篇笔记介绍一下 Golang 里 defer、panic 和 error 的相关概念和操作,以下是本篇笔记目录:

  1. defer
  2. panic
  3. error

1、defer

defer 语句用于延迟执行一个函数,这个函数会在函数返回前执行,无论是正常返回还是触发 panic 之后。

它常用于确保一些文件、链接等资源无论是正常还是异常的情况下被释放。

如果有多个 deder 会按照后进先出的顺序来执行。

1. 后进先出

我们可以先看一个示例:

func main() {
defer fmt.Println("最后输出")
defer fmt.Println("倒数第二个输出") defer fmt.Println("第二个输出")
defer fmt.Println("第一个输出")
}

这个函数执行后,输出的结果如下:

第一个输出
第二个输出
倒数第二个输出
最后输出

从这个操作可以看到,它是满足后进先出的顺序的。

2. 函数返回前执行

接下来我们验证一下它会在函数返回前执行:

func main() {
defer fmt.Println("defer 输出") fmt.Println("正常输出")
fmt.Println("函数的末尾输出")
}

其输出内容如下:

正常输出
函数的末尾输出
defer 输出

可以看到,defer 执行的功能会在函数末尾的操作之后。

3. 异常操作的 defer

接下来我们模拟一下函数中执行异常的情况:

func main() {
defer fmt.Println("defer 输出")
a := 1
b := 0
fmt.Println(a / b)
}

其输出结果如下:

defer 输出
panic: runtime error: integer divide by zero

可以看到,虽然在函数中间执行报错了,但是 defer 后的内容仍然被执行了。

因此,defer 操作可以保证一些资源的释放,即便函数在运行中报错。

4. defer 注意事项

1) defer 函数的参数传递

如果 defer 的函数的使用了参数,那么这个参数在当时就被固定了,即便是后面对其进行了修改,也不会改变,比如下面的例子:

func main() {
i := 1
defer fmt.Println("defer i: ", i) i += 1
fmt.Println("final i: ", i)
}

其输出内容如下:

final i:  2
defer i: 1

可以看到,即便 i 的值被修改,因为 defer 先被执行,所以 i 的值已经被复制,后面对 i 进行修改后,也不会对 defer 操作的内容有所修改。

而如果变量是引用类型,在操作上是一样的,传递的都是变量的副本,但是因为引用类型传递的是引用的副本,而其共享底层数据是一样的,所以修改后会反映到结果上,其示例如下:

func main() {
s := []int{1, 2, 3}
defer fmt.Println("defer s: ", s) s[1] = 100
fmt.Println("final s: ", s)
}

其输出结果为:

final s:  [1 100 3]
defer s: [1 100 3]
2) defer 获取变量最新值

如果我们想要在 defer 逻辑中获取到变量的最新值,我们可以将参数封装到匿名函数内部,延迟到实际执行的时候再求值:

func main() {
i := 1
defer func() { fmt.Println("defer i: ", i) }() i += 1
fmt.Println("final i: ", i)
}

其输出的内容为:

final i:  2
defer i: 2

2、panic

panic 是 Golang 里的一种异常机制,用于处理不可恢复的错误,比如数组越界,空指针引用等。

当发生 panic 时,程序会立即停止当前函数的执行,逐层向上执行 defer 并输出错误信息,下面是一个示例:

func main() {
defer fmt.Println("defer content") fmt.Println("first message") var a []int
fmt.Println(a[0]) fmt.Println("wont show")
}

输出内容如下:

first message
defer content
panic: runtime error: index out of range [0] with length 0 goroutine 1 [running]:
main.main()
/../main.go:32 +0x8f
exit status 2

1. 主动触发和运行时触发

在程序运行过程中,如果发生了我们没有预料到的错误,就会触发 panic,或者当我们在处理中无法继续处理下去,我们也可以选择主动触发 panic。

运行时触发 panic 就比如上面我们介绍的例子,下面介绍一个主动触发 panic 的示例:

func GetRandomState() bool {
num := rand.Intn(5)
if num < 3 {
return true
} else {
return false
}
} func main() {
if GetRandomState() {
panic("random state error")
}
}

这个函数多执行几次,当返回值为 true 时,就会主动触发 panic,并中断程序:

panic: random state error

goroutine 1 [running]:
main.main()
/../main.go:33 +0x38
exit status 2

2. recover 捕获 panic

我们可以使用 recover 用于在 defer 中捕获 panic,使程序恢复正常执行。

注意,这里的恢复正常执行,并不是跳过发生报错的地方接着执行,而是指在调用这个函数之外的地方接着往后执行,从而使整个 Golang 程序不会崩溃。

下面是一个示例:

func Test() {
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered err info: ", r)
}
}() if GetRandomState() {
panic("random state error")
}
} func main() {
Test()
fmt.Println("after Test")
}

当程序报错后,recover 可以捕获到 panic 信息,在这里我们将其打印了出来,并且可以看到,调用 Test() 之后的 fmt.Println() 信息被正常打印出来。

如果是在生产环境,我们可以将其写入日志,或者发送邮件等方式通知到对应的负责人,下面是将错误信息打印出来的结果:

recovered err info:  random state error

根据这个信息,只有简单一个 panic 输出的信息,如果我们想获取更详细的信息,比如,在哪个函数的多少行出了什么报错,我们可以使用下面的操作:

func main() {

    defer func() {
if r := recover(); r != nil {
stackBuffer := make([]byte, 1024)
stackSize := runtime.Stack(stackBuffer, false) fmt.Printf("recovered err info: %s \n", stackBuffer[:stackSize])
}
}() if GetRandomState() {
panic("random state error")
}
}

其中输出的具体的报错信息如下:

recovered err info: goroutine 1 [running]:
main.main.func1()
/../main.go:36 +0x51
panic({0x5a6cf20?, 0x5a855f8?})
/usr/local/go/src/runtime/panic.go:791 +0x132
main.main()
/../main.go:43 +0x5f

可以看到报错信息可以详细到在 main 函数的第 43 行,根据这个信息我们可以再去溯源错误。

3、error

error 是 Golang 的标准错误接口,用于表示错误信息:

type error interface {
Error() string
}

1. 返回 error 信息

我们一般约定函数返回值的最后一个为 error 类型,我们可以使用 errors.New() 的方式来创建一个 error 数据,下面是一个使用示例:

package main

import (
"errors"
"fmt"
) func DivideFunc(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
} func main() {
result, err := DivideFunc(10, 0) if err != nil {
fmt.Println("process error:", err)
return
}
fmt.Println("division result: ", result)
}

在这里,我们获取函数返回值,第一个为结果,第二个为 error 信息,处理 error 信息的时候,判断 err 是否为 nil,不为 nil 则说明函数在运行中发生了报错,需要处理。

除了 errors.New() 这种形式,我们也可以使用 fmt.Errorf("division by zero") 的方式返回一个 error 数据:

func DivideFunc(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}

2. 自定义 error

我们可以自定一个 error 信息,用于输出我们自己想要输出的信息,比如我们定义一个 error 信息如下:

type DivideError struct {
Code int
Msg string
} func (e *DivideError) Error() string {
return fmt.Sprintf("error_code:%d, error_msg:%s", e.Code, e.Msg)
}

在这里,我们使用定义的 DivideError 实现了 error 接口的 Error() 方法,所以可以直接作为 error 类型返回,下面是使用的示例:

func DivideFunc(a, b int) (int, error) {
if b == 0 {
err := &DivideError{Code: -1, Msg: "division by zero"}
return 0, err
}
return a / b, nil
}

3. 检查特定 error 类型

用于判断和检查特定的 error 类型的函数有两个,一个是 errors.Is(),一个是 errors.As()

errors.Is() 用于判断返回的错误信息是否是某个特定错误实例,而 errors.As() 用于判断错误信息是否是某个特定类型,即进行类型断言。

以下是两个函数的使用示例:

1) errors.Is()
type DivideError struct {
Code int
Msg string
} func (e *DivideError) Error() string {
return fmt.Sprintf("error_code:%d, error_msg:%s", e.Code, e.Msg)
} var divideError = &DivideError{Code: -1, Msg: "division by zero"} func DivideFunc(a, b int) (int, error) {
if b == 0 {
return 0, divideError
}
return a / b, nil
} func main() {
result, err := DivideFunc(10, 0) if err != nil {
if errors.Is(err, divideError) {
fmt.Println("divideError: ", err)
} else {
fmt.Println("other error info: ", err)
}
}
fmt.Println("division result: ", result)
}

在这里,先对 DivideError 进行了一个实例化 divideError,在返回错误信息后,使用 errors.Is() 判断是否是该错误。

2) errors.As()

errors.As() 操作如下:

type DivideError struct {
Code int
Msg string
} func (e *DivideError) Error() string {
return fmt.Sprintf("error_code:%d, error_msg:%s", e.Code, e.Msg)
} var divideError = &DivideError{Code: -1, Msg: "division by zero"} func DivideFunc(a, b int) (int, error) {
if b == 0 {
return 0, divideError
}
return a / b, nil
} func main() {
result, err := DivideFunc(10, 0) if err != nil {
var divErr *DivideError
if errors.As(err, &divErr) {
fmt.Println("divideError: ", err, divErr.Code, divErr.Msg)
} else {
fmt.Println("other error info: ", err)
}
}
fmt.Println("division result: ", result)
}

在这里,我们定义了一个 divErr 变量,并使用 errors.As() 函数对其进行类型断言,因此,在后面我们我们可以直接通过 divErr 获取到对应的 CodeMsg 信息。

以上就是本篇笔记关于 deferpanicerror 的相关内容介绍,在实际程序中,我们一般使用 error 来返回一些可预料,可恢复的错误,并在函数调用结束之后捕获到该错误,并决定接下来的程序应该如何操作。

panic 则包括主动触发和运行时的被动触发,主动触发为当我们程序运行中遇上一些可能无法继续运行的错误,我们可以选择 panic 并结束程序运行,而运行时的被动触发则是我们无法预料到的一些错误,从而被动中止程序运行。

在触发 panic 后,程序会中止运行,如果我们有一些需要关闭资源的操作,我们可以在开启资源调用的时候就使用 defer 操作来预防程序中止后无法关闭资源的问题,同时我们可以在 defer 中使用 recover 操作来捕获到 panic 信息并输出到日志或采用其他能通知到程序运行者的方式。

Golang基础笔记十二之defer、panic、error的更多相关文章

  1. Django笔记十二之defer、only指定返回字段

    本篇笔记为Django笔记系列之十二,首发于公号[Django笔记] 本篇笔记将介绍查询中的 defer 和 only 两个函数的用法,笔记目录如下: defer only 1.defer defer ...

  2. Bootstrap <基础三十二>模态框(Modal)插件

    模态框(Modal)是覆盖在父窗体上的子窗体.通常,目的是显示来自一个单独的源的内容,可以在不离开父窗体的情况下有一些互动.子窗体可提供信息.交互等. 如果您想要单独引用该插件的功能,那么您需要引用  ...

  3. 《C++游戏开发》笔记十二 战争迷雾:初步实现

    本系列文章由七十一雾央编写,转载请注明出处.  http://blog.csdn.net/u011371356/article/details/9475979 作者:七十一雾央 新浪微博:http:/ ...

  4. python3.4学习笔记(十二) python正则表达式的使用,使用pyspider匹配输出带.html结尾的URL

    python3.4学习笔记(十二) python正则表达式的使用,使用pyspider匹配输出带.html结尾的URL实战例子:使用pyspider匹配输出带.html结尾的URL:@config(a ...

  5. Go语言学习笔记十二: 范围(Range)

    Go语言学习笔记十二: 范围(Range) rang这个关键字主要用来遍历数组,切片,通道或Map.在数组和切片中返回索引值,在Map中返回key. 这个特别像python的方式.不过写法上比较怪异使 ...

  6. DirectX11笔记(十二)--Direct3D渲染8--EFFECTS

    原文:DirectX11笔记(十二)--Direct3D渲染8--EFFECTS 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/u010333737 ...

  7. Java 多线程基础(十二)生产者与消费者

    Java 多线程基础(十二)生产者与消费者 一.生产者与消费者模型 生产者与消费者问题是个非常典型的多线程问题,涉及到的对象包括“生产者”.“消费者”.“仓库”和“产品”.他们之间的关系如下: ①.生 ...

  8. Golang 入门系列(十四)defer, panic和recover用法

    以前讲过golang 的基本语法.但是,只是讲了一些基础的语法,感兴趣的可以看看以前的文章,https://www.cnblogs.com/zhangweizhong/category/1275863 ...

  9. Python3基础(十二) 学习总结·附PDF

    Python是一门强大的解释型.面向对象的高级程序设计语言,它优雅.简单.可移植.易扩展,可用于桌面应用.系统编程.数据库编程.网络编程.web开发.图像处理.人工智能.数学应用.文本处理等等. 在学 ...

  10. java jvm学习笔记十二(访问控制器的栈校验机制)

    欢迎装载请说明出处:http://blog.csdn.net/yfqnihao 本节源码:http://download.csdn.net/detail/yfqnihao/4863854 这一节,我们 ...

随机推荐

  1. Intellij, target JRE vesion doesn't match project jdk version

    gradle 项目修改这里的gradle jvm

  2. IntelliJ IDEA实用插件推荐,提高开发效率

    idea插件推荐 工欲善其事必先利其器,想要提高开发效率,站在巨人的肩膀上走捷径无疑是最快的!下面就给开发者朋友们推荐一些Idea实用的.装逼酷炫的插件! 实用级 GsonFormat (json格式 ...

  3. 网鼎杯朱雀组-GO

    这里猜测是魔改base64 尝试替换回去 import string import base64 new="XYZFGHI2+/Jhi345jklmEnopuvwqrABCDKL6789ab ...

  4. PC端自动化测试实战教程-3-pywinauto 启动PC端应用程序 - 下篇(详细教程)

    1.简介 经过上一篇的学习.介绍和了解,pywinauto的强大,不言而喻吧!宏哥讲解和分享的是电脑自带和安装的应用程序.有些小伙伴或者童鞋们已经迫不及待地私信宏哥,如果在电脑中这个应用程序已经启用了 ...

  5. 记一次移动光猫(GM219-S)安全测试

    前言 过个年,WiFi密码忘记了-光猫管理密码也忘记了(这个光猫也不支持物理按钮重置设置),但是手机还连着WiFi,正规操作找回不了密码,那就用咱们测试的思维来试试PWN掉这个路由器. 过程 未授权获 ...

  6. 代码随想录第七天 | Leecode 454.四数相加II 、383. 赎金信 、15. 三数之和 、18. 四数之和

    Leecode 454. 四数相加II 题目链接:https://leetcode.cn/problems/4sum-ii/ 题目描述 给你四个整数数组 nums1.nums2.nums3 和 num ...

  7. 反悔贪心&局部调整法学习笔记

    一.什么是反悔贪心 反悔贪心就是在普通贪心的过程中"反悔",从而使得一些看似不太好贪心的题变成贪心可做题. 二.反悔贪心普遍流程 就是先使用一个好想的贪心策略,使用优先队列进行维护 ...

  8. FastMCP(python)和 SolonMCP(java)的体验比较(不能说一样,但真的很像)

    从 MCP SDK 的发展史上看,FastMCP 是前辈,SolonMCP 则是后辈.mcp-python-sdk 功能完善,已经很成熟了.而 mcp-java-sdk 却还不完善,比如: 还不支持 ...

  9. 【Java持久层技术演进全解析】从JDBC到MyBatis再到MyBatis-Plus

    从JDBC到MyBatis再到MyBatis-Plus:Java持久层技术演进全解析 引言 在Java企业级应用开发中,数据持久化是核心需求之一.本文将系统性地介绍Java持久层技术的演进过程,从最基 ...

  10. windows11 安装CUDA Toolkit,Python,Anaconda,PyTorch并使用DeepSeek 多模态模型 Janus-Pro识别和生成图片

    一.概述 因为公司网络做了严格限制,必须使用账号登录,才能上网.必须是指定的ip地址和MAC地址设备才可以上网. windows11开启热点,安装第三方虚拟机软件,开启WSL2虚拟机都是被禁止的,否则 ...