go错误处理——return,panic,defer

return和defer的顺序:

return先返回,再运行defer,可以修改返回值:

package main

import (
    "fmt"
)

func main() {
    fmt.Println(test())
}
//返回值为2
func test() (result int) {
    defer func() {
        result++
    }()
    return 1 //return函数先返回 result被赋值为1 然后再result++
}

这里有个比较好玩儿的点,因为返回值是具名的,所以我们return 1是对result赋值为1了。

另外如果是不具名的返回值,但是你return了一个具名的变量,是否会对返回值产生修改作用。

package main

import (
    "fmt"
)

func main() {
    fmt.Println(test())
}
//输出为0
func test() int {
    var a int
    defer func() {
        a++
    }()
    return a 
}

证明不能对返回值进行修改了,具体跟作用域应该有关系,有时间再深究。当然如果将a返回给具名的返回值result然后修改result当然能修改返回值。

项目中遇到过的使用

在对panic的处理中,将panic的错误信息作为返回值,简化版代码如下:

package main

import (
    "fmt"
    "runtime/debug"
)

func main() {
    fmt.Println(test())
}
func test() (err error) {
    defer func() {
        if e := recover(); e != nil {
            err = fmt.Errorf("%s \n panic:\n %s", e, debug.Stack())
            //打印e(panic的异常信息补货结果),并且强烈推荐打印堆栈信息用来定位错误。
        }
    }()
    var a int
    a = 1 / a
    return nil
}

返回结果如下:

runtime error: integer divide by zero 
 panic:
 goroutine 1 [running]:
runtime/debug.Stack(0xc042031d88, 0x49d220, 0xc042004060)
    C:/Go/src/runtime/debug/stack.go:24 +0x80
main.test.func1(0xc042031ea8)
    C:/Users/colinhome/Documents/GitHub/test/src/structindex/base64.go:14 +0x6a
panic(0x49d220, 0xc042004060)
    C:/Go/src/runtime/panic.go:458 +0x251
main.test(0x0, 0x0)
    C:/Users/colinhome/Documents/GitHub/test/src/structindex/base64.go:19 +0x80
main.main()
    C:/Users/colinhome/Documents/GitHub/test/src/structindex/base64.go:9 +0x3b

能定位到具体行数。方便调试。

### Go语言中 `defer` 的使用场景和工作原理 #### 1. **延迟调用** `defer` 关键字的主要功能是在函数返回前执行某些操作。无论函数是正常退出还是因异常(panic)退出,`defer` 都会被触发[^1]。 #### 2. **资源管理** `defer` 常用于资源的自动释放,比如文件关闭、网络连接断开或锁的解锁等。这有助于避免资源泄漏并简化代码结构。例如: ```go func readFile(filename string) { file, err := os.Open(filename) if err != nil { log.Fatal(err) } defer file.Close() // 确保文件在函数结束时被关闭 // 文件读取逻辑... } ``` 上述代码展示了如何利用 `defer` 自动关闭文件描述符[^5]。 #### 3. **错误处理** 在复杂的业务逻辑中,`defer` 可以用来统一处理错误状态或其他清理任务。例如: ```go func processData(data []byte) (err error) { var conn net.Conn conn, err = net.Dial("tcp", "example.com:80") if err != nil { return err } defer func() { if r := recover(); r != nil { fmt.Println("Recovered from panic:", r) } _ = conn.Close() }() // 数据处理逻辑... return nil } ``` 这里不仅实现了资源释放,还通过匿名函数捕获潜在的 `panic` 并恢复程序运行[^2]。 #### 4. **执行顺序** 当存在多个 `defer` 调用时,它们遵循后进先出(LIFO)原则执行。也就是说,最晚声明的 `defer` 会最早被执行。例如: ```go func example() { defer fmt.Println("First Defer") defer fmt.Println("Second Defer") defer fmt.Println("Third Defer") } // 输出结果为: // Third Defer // Second Defer // First Defer ``` 此行为由设计决定,便于开发者更好地控制清理过程[^4]。 #### 5. **参数值的计算时机** 需要注意的是,`defer` 中传递给目标函数的实际参数会在定义时刻求值而非调用时刻。考虑下面的例子: ```go func main() { i := 5 defer fmt.Println(i) // 此处打印的是当前i的值(5),即便之后修改也不会影响 i++ } ``` 最终输出仍为初始赋值的结果——即 `5`,因为变量绑定发生在创建阶段[^5]。 --- ### 工作原理概述 从实现角度来看,每次遇到 `defer` 表达式时,编译器都会将其记录在一个栈数据结构之中;等到所在函数即将完成之际,则依照逆序逐一弹出这些待办事项加以履行[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值