在Go语言中,panic是一个内置函数,用于引发运行时错误,导致程序异常终止。下面详细讲解panic的使用和特性。
func main() {
fmt.Println("程序开始执行")
// 触发panic
panic("发生了严重错误!")
fmt.Println("这行代码不会被执行") // 不会执行
}
输出:
程序开始执行
panic: 发生了严重错误!
goroutine 1 [running]:
main.main()
/tmp/sandbox/main.go:8 +0x65
func divide(a, b int) int {
if b == 0 {
panic("除数不能为零")
}
return a / b
}
func main() {
// 数组越界
arr := [3]int{1, 2, 3}
fmt.Println(arr[5]) // panic: runtime error: index out of range
// 空指针解引用
var ptr *int
fmt.Println(*ptr) // panic: runtime error: invalid memory address
// 关闭未初始化的channel
var ch chan int
close(ch) // panic: close of nil channel
}
func testPanic() {
defer fmt.Println("defer语句被执行") // 会执行
fmt.Println("函数开始执行")
panic("触发panic")
fmt.Println("这行不会执行")
}
func main() {
testPanic()
}
输出:
函数开始执行
defer语句被执行
panic: 触发panic
func testMultipleDefer() {
defer fmt.Println("第一个defer")
defer fmt.Println("第二个defer")
defer fmt.Println("第三个defer")
panic("测试panic")
}
func main() {
testMultipleDefer()
}
输出:
第三个defer
第二个defer
第一个defer
panic: 测试panic
func safeDivide(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("运行时错误: %v", r)
}
}()
if b == 0 {
panic("除数不能为零")
}
return a / b, nil
}
func main() {
// 正常情况
result, err := safeDivide(10, 2)
if err != nil {
fmt.Println("错误:", err)
} else {
fmt.Println("结果:", result) // 输出: 结果: 5
}
// 触发panic的情况
result, err = safeDivide(10, 0)
if err != nil {
fmt.Println("错误:", err) // 输出: 错误: 运行时错误: 除数不能为零
} else {
fmt.Println("结果:", result)
}
}
func incorrectRecover() {
// 错误:直接调用recover,不在defer中
if r := recover(); r != nil {
fmt.Println("捕获到panic:", r)
}
panic("这个panic无法被捕获")
}
func correctRecover() {
defer func() {
if r := recover(); r != nil {
fmt.Println("成功捕获panic:", r)
}
}()
panic("这个panic会被捕获")
}
func processTransaction(db *sql.DB) (err error) {
tx, err := db.Begin()
if err != nil {
return err
}
// 确保在panic时回滚事务
defer func() {
if r := recover(); r != nil {
tx.Rollback()
err = fmt.Errorf("事务处理失败: %v", r)
}
}()
// 执行数据库操作
_, err = tx.Exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
if err != nil {
tx.Rollback()
return err
}
_, err = tx.Exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
if err != nil {
tx.Rollback()
return err
}
return tx.Commit()
}
func processFile(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("打开文件失败:", err)
return
}
// 确保文件关闭
defer func() {
file.Close()
fmt.Println("文件已关闭")
}()
// 处理文件过程中可能发生panic
processor := NewFileProcessor()
processor.Process(file) // 可能触发panic
}
// 不推荐:对预期内的错误使用panic
func validateInput(input string) {
if input == "" {
panic("输入不能为空") // 应该返回error
}
}
// 推荐:返回error
func validateInput(input string) error {
if input == "" {
return errors.New("输入不能为空")
}
return nil
}
func main() {
// 在main函数顶层恢复,防止程序崩溃
defer func() {
if r := recover(); r != nil {
fmt.Fprintf(os.Stderr, "程序发生严重错误: %v\n", r)
// 记录日志、清理资源等
os.Exit(1)
}
}()
runApplication()
}
func loadConfig(path string) *Config {
data, err := os.ReadFile(path)
if err != nil {
panic(fmt.Sprintf("无法加载配置文件 %s: %v", path, err))
}
var config Config
if err := json.Unmarshal(data, &config); err != nil {
panic(fmt.Sprintf("配置文件格式错误 %s: %v", path, err))
}
return &config
}
合理使用panic和recover可以编写出更健壮的Go程序,但应该避免过度使用panic来处理正常的错误情况。