Skip to content

第三章 流程控制与错误处理 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、跳转语句

  1. break语句: 用于跳出终止最内层的 for 循环。

    go
    for i := 0; i < 10; i++ {
        if i == 5 {
            break
        }
        fmt.Println(i)
    }
  2. continue语句: 用于跳过当前循环的剩余部分,并开始下一次循环迭代

    go
    for i := 0; i < 10; i++ {
        if i%2 == 0 {
            continue
        }
        fmt.Println(i)
    }
  3. goto语句(一般不推荐常规场景使用): 无条件跳转到程序中的指定标签

    go
    func 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
// defer
go
func example() {
    defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")
    fmt.Println("begin")
}
/// example函数运行结果:
// begin
// 3
// 2
// 1
go
// 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 = 10
go
// 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,而是通过panicrecover实现“非预期错误”的处理,核心是“控制程序崩溃范围”,避免全局崩溃。

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 1

2.recover

go
/// recover用于在goroutine中捕获panic并恢复正常执行流程
// 返回值:panic传入的错误值
// *注意:仅在defer声明的函数中有效
func recover() any
go
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对应的值
}