第三章 流程控制与错误处理 v1.0
第一部分 流程控制
一、条件结构
1、if语句
go
if 判断条件 { //注意:此处没有括号
执行语句
} else if [初始化表达式;]判断条件 {
执行语句
} else {
执行语句
} //初始化表达式可以使用":="初始化一个局部变量go
if 初始化表达式;判断条件 {
执行语句
} else {
执行语句
}2、switch语句
注意:Go语言中switch语句不需要break,执行语句后会自动跳出,使用
fallthrough关键字可以重新强制执行下一个case代码块
go
switch 变量名 { //标准结构
case 变量值:
执行语句
case 变量值1, 变量值2, ...: // 多条件,符合一个即执行语句
执行语句
default:
执行语句
}go
switch { //Go语言特殊结构
case 判断条件:
执行语句
default:
执行语句
}go
func main() {
x := 1
switch x {
case 1:
fmt.Println("One")
fallthrough
case 2:
fmt.Println("Two")
default:
fmt.Println("Other")
}
} // 输出:One\nTwo二、循环结构
1、for循环
go
for n:=0 ; n<10 ; n++ {
执行语句
}go
for i := range 待遍历切片 {
执行语句
}
for i,j := range 待遍历字典 {
执行语句
}go
for 判断条件 { //判断条件不写默认为真
执行语句
}2、跳转语句
break语句: 用于跳出终止最内层的 for 循环。gofor i := 0; i < 10; i++ { if i == 5 { break } fmt.Println(i) }continue语句: 用于跳过当前循环的剩余部分,并开始下一次循环迭代gofor i := 0; i < 10; i++ { if i%2 == 0 { continue } fmt.Println(i) }goto语句(一般不推荐常规场景使用): 无条件跳转到程序中的指定标签gofunc main() { i := 0 Here: // 标签:需以':'结尾,且与goto语句在同一函数内 fmt.Println(i) i++ if i < 5 { goto Here // 跳转到Here标签 } }
三、延迟执行语句
defer用于声明一个函数或语句块,使其在当前函数执行结束前(无论正常返回还是异常退出)延迟执行,保证资源释放,避免遗漏清理操作
特性:
- 执行时机:在包含
defer的函数返回(return/panic)前执行 - 执行顺序:多个
defer语句执行顺序遵循栈结构——先声明的后执行 - 作用域:当前函数,函数退出后
defer才会触发(即使在循环/条件语句中声明)
底层实现逻辑:在声明时将defer语句按顺序放入一个栈结构,函数退出时再从栈顶取出并执行
go
func example() {
defer fmt.Println("defer") // 延迟执行
fmt.Println("main")
}
/// example函数运行结果:
// main
// defergo
func example() {
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
fmt.Println("begin")
}
/// example函数运行结果:
// begin
// 3
// 2
// 1go
// defer声明的函数参数在声明时就会计算,后续修改变量也不会影响defer函数的实际参数值
func example() {
x := 10
defer fmt.Println("defer:x =", x) // 声明时x=10,参数已确定
x = 20
fmt.Println("x =", x)
}
/// example函数运行结果:
// x = 20
// defer:x = 10go
// defer中修改返回值,可能影响最终结果(取决于返回值的声明方式)
func returnWithDefer() int {
x := 10
defer func() {
x = 20 // x为局部变量
}()
return x // 返回时x=10,defer修改不影响
}
func returnNamedWithDefer() (x int) {
x = 10
defer func() {
x = 20 // x为命名返回值
}()
return // 最终返回20
}第二部分 异常处理
一、错误
go
type error interface {
Error() string
}任何实现了 Error 方法的类型都可以被视为一个错误。
go
// Go 标准库提供了errors包来创建基本的错误
import "errors"
err := errors.New("an error")go
// 自定义的错误类型
type MyError struct {
Code int
Message string
}
// MyError 实现了 error 接口,被视为一个错误
func (e *MyError) Error() string {
return fmt.Sprintf("Code: %d, Message: %s", e.Code, e.Message)
}二、错误链
1.关联错误链
go
// 在fmt.Errorf中使用%w关键字关联到错误链中
if err != nil {
return fmt.Errorf(“error: %w”, err) //err为关联的错误
}2.错误的判定与转换
go
// 判定错误链上的所有错误是否含特定错误
errors.Is(err, fs.ErrXxx)go
err := processRequest()
var targetErr *MyError
if errors.As(err, &targetErr) { // 存在错误则转换并处理,否则作为通用错误处理
fmt.Println("The error is of type *MyError")
}三、错误捕获边界
Golang不提供try/catch,而是通过
panic和recover实现“非预期错误”的处理,核心是“控制程序崩溃范围”,避免全局崩溃。
Go语言中,panic用于触发异常中断程序执行,而recover用于捕获异常并恢复程序运行。
1.panic
go
/// panic用于触发异常,中断当前goroutine的正常执行流程,向上层调用者传递panic
// 当panic向上层传递时,调用者会像被调用了panic一样,重复上述过程;当传播完当前goroutine的所有函数时,程序会以非零退出码终止
// 参数:错误值,可以是任意类型(字符串、error等)
func panic(v any)go
func initConfig() {
config, err := loadConfig()
if err != nil {
// 初始化失败,程序无法正常运行,主动触发panic
panic(fmt.Sprintf("配置加载失败:%v", err))
}
}go
// panic时,defer仍会正常执行
func panicWithDefer() {
defer fmt.Println("defer 1")
defer func() {
if err := recover(); err != nil {
fmt.Println("recover:", err)
}
}()
defer fmt.Println("defer 2")
panic("panic")
fmt.Println("over")
}
// 输出:
// defer 2
// recover: panic
// defer 12.recover
go
/// recover用于在goroutine中捕获panic并恢复正常执行流程
// 返回值:panic传入的错误值
// *注意:仅在defer声明的函数中有效
func recover() anygo
func handleRequest(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
// 捕获panic,返回500错误,避免服务崩溃
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w, "服务内部错误")
log.Printf("请求异常:%v", err)
}
}()
// 业务逻辑
panic("程序异常!")
}第三部分 上下文控制(context)
Context类型是context包核心概念,代表了执行请求的全部生命周期,包括取消、截止时间、值传递等功能
一、创建
context包主要提供两种方式创建context,两种方式实际没有区别
go
context.Backgroud() // 官方定义:上下文默认值,其他上下文都应从他派生出来
context.TODO() // 官方定义:不确定应使用哪种上下文时使用,应尽早替换为具体上下文二、派生
根context不具备任何功能,使其具备功能需要进行派生
go
// 派生可取消上下文
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)go
// 派生绝对截止时间上下文
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)go
// 派生相对超时时间上下文
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)go
// 派生携带数据的上下文
func WithValue(parent Context, key, val interface{}) Context派生示例
go
func main() {
ctx1, cancel1 := context.WithCancel(context.Background())
ctx2, cancel2 := context.WithCancel(context.Background())
defer cancel1() // 确保函数退出时调用取消函数
defer cancel2() // 确保函数退出时调用取消函数
go func() {
time.Sleep(1 * time.Second)
cancel1() // 1秒后取消操作
time.Sleep(5 * time.Second)
cancel2() // 5秒后取消
}()
select {
case <-time.After(2 * time.Second):
fmt.Println("ctx1 operation completed")
case <-ctx1.Done():
fmt.Println("ctx1 operation cancelled") // 输出结果
}
select {
case <-time.After(2 * time.Second):
fmt.Println("ctx2 operation completed") // 输出结果
case <-ctx2.Done():
fmt.Println("ctx2 operation cancelled")
}
}go
func main() {
deadline := time.Now().Add(3 * time.Second) // 设置deadline为3秒后
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
select {
case <-time.After(5 * time.Second):
fmt.Println("operation completed")
case <-ctx.Done():
fmt.Println("operation timed out") // 输出结果
}
}go
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
select {
case <-time.After(5 * time.Second):
fmt.Println("operation completed")
case <-ctx.Done():
fmt.Println("operation timed out") // 输出结果
}
}go
func main() {
ctx := context.WithValue(context.Background(), "userID", 10000)
processRequest(ctx)
}
func processRequest(ctx context.Context) {
userID := ctx.Value("userID").(int)
fmt.Printf("Processing request for user ID: %d\n", userID) // 输出:Processing request for user ID: 10000
}基于一个父Context可以随意衍生,形成一棵Context树,树的每个节点都可以有任意多个子节点
text
Context.Background()
`-> ctx
|- WithCancel(ctx) -> ctxWithCancel
|- WithDeadline(ctx, t) -> ctxWithDeadline
|- WithTimeout(ctx, t) -> ctxWithTimeout
`- WithValue(ctx, k, v) -> ctxWithValue
`-> WithXxx(ctxWithValue, xxx) -> ctxXxx三、使用
go
// 返回 Context 的截止时间和一个布尔值,表示是否设置了截止时间
deadline, status := ctx.Deadline()
if status {
fmt.Println("Deadline set:", deadline)
} else {
fmt.Println("No deadline set")
}go
// 返回一个通道,当 Context 被取消或超时时,该通道会被关闭,返回{}
select {
case <-ctx.Done(): // 超时取消后返回{}得到响应
fmt.Println("Context done:", ctx.Err())
case <-time.After(5 * time.Second):
fmt.Println("Operation completed")
}go
// 返回 Context 结束的原因,如果 Context 还未结束,返回 nil
if err := ctx.Err(); err != nil {
fmt.Println("Context error:", err)
}go
// 返回与 Context 关联的键对应的值,如果没有对应的值,返回 nil
userID := ctx.Value("userID").(int)
fmt.Printf("User ID: %d\n", userID)四、自定义
Context本质是一个接口,其定义了四个方法,实现该接口即可自定义Context
go
type Context interface {
Deadline() (deadline time.Time, ok bool) // 自动取消或超时取消后返回
Done() <-chan struct{} // 当Context被取消或关闭后,返回一个被关闭的channel
Err() error // 当Context被取消或关闭后,返回context取消的原因
Value(key interface{}) interface{} // 获取设置的key对应的值
}