Skip to content

控制流和错误处理

1:Go的条件语句和循环语句

详细解答

1. if语句的特殊用法

代码实现
go
// 基本if语句
func basicIf(x int) {
    if x > 0 {
        fmt.Println("x是正数")
    } else if x < 0 { // go语言不允许else if 和 else 换行
        fmt.Println("x是负数")
    } else {
        fmt.Println("x是零")
    }
}

// if语句中的初始化
func ifWithInit() {
    // 在if条件中声明变量,后续的else if 和else可以访问,出了if不可访问
    if num := rand.Intn(100); num < 50 {
        fmt.Printf("随机数 %d 小于50\n", num)
    } else {
        fmt.Printf("随机数 %d 大于等于50\n", num)
    }
    // num在这里不可访问
    
    // 常见的错误处理模式
    if file, err := os.Open("test.txt"); err != nil {
        fmt.Printf("打开文件失败: %v\n", err)
    } else {
        defer file.Close()
        fmt.Println("文件打开成功")
    }
}

2. switch语句的多种形式

和其他语言相比,自带break,想要穿透,需要fallthrough

  • switch表达式是可选的,如果省略,等价于switch true,即表达式switch
  • case允许匹配多个条件
代码实现
go
// 基本switch语句
func basicSwitch(day int) {
    switch day {
    case 1:
        fmt.Println("星期一")
    case 2:
        fmt.Println("星期二")
    case 3, 4, 5:  // 多个case值
        fmt.Println("工作日")
    case 6, 7:
        fmt.Println("周末")
    default:
        fmt.Println("无效的日期")
    }
}

// 表达式switch
func expressionSwitch(score int) {
    switch {
    case score >= 90:
        fmt.Println("优秀")
    case score >= 80:
        fmt.Println("良好")
    case score >= 70:
        fmt.Println("中等")
    case score >= 60:
        fmt.Println("及格")
    default:
        fmt.Println("不及格")
    }
}

// switch初始化语句
func switchWithInit() {
    switch hour := time.Now().Hour(); {
    case hour < 12:
        fmt.Println("上午好")
    case hour < 18:
        fmt.Println("下午好")
    default:
        fmt.Println("晚上好")
    }
}

// 类型switch
func typeSwitch(v interface{}) {
    switch x := v.(type) {
    case nil:
        fmt.Println("v是nil")
    case bool:
        fmt.Printf("v是bool类型,值为%t\n", x)
    case int:
        fmt.Printf("v是int类型,值为%d\n", x)
    case string:
        fmt.Printf("v是string类型,值为%s\n", x)
    default:
        fmt.Printf("v是%T类型\n", x)
    }
}

3. for循环的各种形式

for range 循环遍历切片时,切片长度在开始遍历时就已经被固定下来,即使循环中使用 append 动态修改切片的长度,也不会影响 range 的遍历次数。

代码实现
go
// 经典for循环
func classicFor() {
    for i := 0; i < 5; i++ {
        fmt.Printf("%d ", i)
    }
    fmt.Println()
}

// while风格的for循环
func whileStyleFor() {
    i := 0
    for i < 5 {
        fmt.Printf("%d ", i)
        i++
    }
    fmt.Println()
}

// 无限循环
func infiniteFor() {
    count := 0
    for {
        count++
        if count > 3 {
            break
        }
        fmt.Printf("计数: %d\n", count)
    }
}

// for-range循环
func forRange() {
    // 遍历切片
    numbers := []int{1, 2, 3, 4, 5}
    for index, value := range numbers {
        fmt.Printf("索引%d: 值%d\n", index, value)
    }
    
    // 只要索引
    for index := range numbers {
        fmt.Printf("索引: %d\n", index)
    }
    
    // 只要值
    for _, value := range numbers {
        fmt.Printf("值: %d\n", value)
    }
    
    // 遍历字符串
    text := "Hello世界"
    for index, char := range text {
        fmt.Printf("字节索引%d: 字符%c\n", index, char)
    }
    
    // 遍历map
    ages := map[string]int{
        "Alice": 30,
        "Bob":   25,
    }
    for name, age := range ages {
        fmt.Printf("%s: %d\n", name, age)
    }
    
    // 遍历通道
    ch := make(chan int, 3)
    ch <- 1
    ch <- 2
    ch <- 3
    close(ch)
    
    for value := range ch {
        fmt.Printf("从通道接收: %d\n", value)
    }
}

2:Go的错误处理机制

1. 基本错误处理

代码实现
go
import (
    "errors"
    "fmt"
    "strconv"
)

// 返回错误的函数
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("除数不能为零")
    }
    return a / b, nil
}

// 基本错误处理模式
func basicErrorHandling() {
    result, err := divide(10, 2)
    if err != nil {
        fmt.Printf("错误: %v\n", err)
        return
    }
    fmt.Printf("结果: %f\n", result)
    
    // 处理除零错误
    result, err = divide(10, 0)
    if err != nil {
        fmt.Printf("除法错误: %v\n", err)
        // 可以进行错误恢复或其他处理
        return
    }
    fmt.Printf("结果: %f\n", result)
}

2. 自定义错误类型

代码实现
go
// 自定义错误类型
type ValidationError struct {
    Field   string
    Message string
}

func (e ValidationError) Error() string {
    return fmt.Sprintf("验证失败 - %s: %s", e.Field, e.Message)
}

// 使用自定义错误
func validateUser(name string, age int) error {
    if name == "" {
        return ValidationError{
            Field:   "name",
            Message: "姓名不能为空",
        }
    }
    
    if age < 0 || age > 150 {
        return ValidationError{
            Field:   "age", 
            Message: "年龄必须在0-150之间",
        }
    }
    
    return nil
}

func demonstrateCustomError() {
    if err := validateUser("", 25); err != nil {
        fmt.Printf("用户验证失败: %v\n", err)
        
        // 类型断言检查具体错误类型
        if ve, ok := err.(ValidationError); ok {
            fmt.Printf("验证错误字段: %s\n", ve.Field)
        }
    }
}

3. 错误包装和解包(Go 1.13+)

代码实现
go
import (
    "fmt"
    "errors"
)

// 错误包装
func processFile(filename string) error {
    if err := readFile(filename); err != nil {
        return fmt.Errorf("处理文件 %s 失败: %w", filename, err)
    }
    return nil
}

func readFile(filename string) error {
    // 模拟文件读取错误
    return errors.New("文件不存在")
}

// 错误解包和检查
func demonstrateErrorWrapping() {
    err := processFile("test.txt")
    if err != nil {
        fmt.Printf("错误: %v\n", err)
        
        // 检查错误链中是否包含特定错误
        var target error = errors.New("文件不存在")
        if errors.Is(err, target) {
            fmt.Println("错误链中包含文件不存在错误")
        }
        
        // 解包错误
        if unwrapped := errors.Unwrap(err); unwrapped != nil {
            fmt.Printf("原始错误: %v\n", unwrapped)
        }
    }
}

4. 错误处理的最佳实践

代码实现
go
// 多返回值的错误处理
func parseAndValidate(input string) (int, error) {
    // 首先解析
    num, err := strconv.Atoi(input)
    if err != nil {
        return 0, fmt.Errorf("解析数字失败: %w", err)
    }
    
    // 然后验证
    if num < 0 {
        return 0, errors.New("数字不能为负数")
    }
    
    return num, nil
}

// 早期返回模式
func processData(data []string) error {
    if len(data) == 0 {
        return errors.New("数据为空")
    }
    
    for i, item := range data {
        if item == "" {
            return fmt.Errorf("第%d项数据为空", i)
        }
        
        num, err := parseAndValidate(item)
        if err != nil {
            return fmt.Errorf("处理第%d项数据失败: %w", i, err)
        }
        
        // 处理解析后的数字
        fmt.Printf("处理数字: %d\n", num)
    }
    
    return nil
}

// 批量错误收集
func processDataBatch(data []string) []error {
    var errors []error
    
    for i, item := range data {
        if _, err := parseAndValidate(item); err != nil {
            errors = append(errors, 
                fmt.Errorf("第%d项: %w", i, err))
        }
    }
    
    return errors
}

func demonstrateErrorPatterns() {
    // 早期返回模式
    data1 := []string{"10", "20", "-5", "30"}
    if err := processData(data1); err != nil {
        fmt.Printf("处理失败: %v\n", err)
    }
    
    // 批量错误收集
    data2 := []string{"10", "abc", "-5", "30"}
    if errs := processDataBatch(data2); len(errs) > 0 {
        fmt.Println("批量处理错误:")
        for _, err := range errs {
            fmt.Printf("  %v\n", err)
        }
    }
}

3:break、continue和标签

1. break和continue基础用法

代码实现
go
func basicBreakContinue() {
    fmt.Println("=== break示例 ===")
    for i := 0; i < 10; i++ {
        if i == 5 {
            break  // 跳出循环
        }
        fmt.Printf("%d ", i)  // 输出: 0 1 2 3 4
    }
    fmt.Println()
    
    fmt.Println("=== continue示例 ===")
    for i := 0; i < 10; i++ {
        if i%2 == 0 {
            continue  // 跳过偶数
        }
        fmt.Printf("%d ", i)  // 输出: 1 3 5 7 9
    }
    fmt.Println()
}

2. 嵌套循环中的标签使用

代码实现
go
func labeledBreakContinue() {
    fmt.Println("=== 标签break示例 ===")
outer:
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            if i == 1 && j == 1 {
                break outer  // 跳出外层循环
            }
            fmt.Printf("(%d,%d) ", i, j)
        }
    }
    fmt.Println()
    
    fmt.Println("=== 标签continue示例 ===")
outer2:
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            if j == 1 {
                continue outer2  // 继续外层循环的下一次迭代
            }
            fmt.Printf("(%d,%d) ", i, j)
        }
    }
    fmt.Println()
}

3. 实际应用场景

代码实现
go
// 查找二维数组中的元素
func findInMatrix(matrix [][]int, target int) (row, col int, found bool) {
search:
    for i, rowData := range matrix {
        for j, value := range rowData {
            if value == target {
                row, col, found = i, j, true
                break search
            }
        }
    }
    return
}

// 处理多层验证
func validateInput(data map[string]interface{}) error {
validation:
    for key, value := range data {
        switch key {
        case "name":
            if str, ok := value.(string); !ok || str == "" {
                break validation
            }
        case "age": 
            if age, ok := value.(int); !ok || age < 0 {
                break validation
            }
        case "email":
            if email, ok := value.(string); !ok || !strings.Contains(email, "@") {
                break validation
            }
        }
        continue validation
    }
    
    return errors.New("验证失败")
}

func demonstrateRealWorldUsage() {
    // 测试矩阵查找
    matrix := [][]int{
        {1, 2, 3},
        {4, 5, 6}, 
        {7, 8, 9},
    }
    
    if row, col, found := findInMatrix(matrix, 5); found {
        fmt.Printf("找到元素5在位置(%d,%d)\n", row, col)
    }
    
    // 测试数据验证
    testData := map[string]interface{}{
        "name":  "Alice",
        "age":   30,
        "email": "alice@example.com",
    }
    
    if err := validateInput(testData); err != nil {
        fmt.Printf("验证失败: %v\n", err)
    } else {
        fmt.Println("验证通过")
    }
}

4:select语句和通道操作

1. select基础用法

go
func basicSelect() {
    ch1 := make(chan string, 1)
    ch2 := make(chan string, 1)
    
    ch1 <- "来自ch1的消息"
    
    select {
    case msg1 := <-ch1:
        fmt.Printf("接收到: %s\n", msg1)
    case msg2 := <-ch2:
        fmt.Printf("接收到: %s\n", msg2)
    default:
        fmt.Println("没有可用的通道操作")
    }
}

2. select超时模式

go
import "time"

func selectWithTimeout() {
    ch := make(chan string)
    
    // 在另一个goroutine中延迟发送数据
    go func() {
        time.Sleep(2 * time.Second)
        ch <- "延迟消息"
    }()
    
    select {
    case msg := <-ch:
        fmt.Printf("接收到消息: %s\n", msg)
    case <-time.After(1 * time.Second):
        fmt.Println("操作超时")
    }
}

func selectNonBlocking() {
    ch := make(chan int, 1)
    
    // 非阻塞发送
    select {
    case ch <- 42:
        fmt.Println("成功发送数据")
    default:
        fmt.Println("通道已满,无法发送")
    }
    
    // 非阻塞接收
    select {
    case value := <-ch:
        fmt.Printf("接收到: %d\n", value)
    default:
        fmt.Println("通道为空,无法接收")
    }
}

3. 复杂的select应用

代码实现
go
func multiChannelProcessor() {
    data := make(chan int, 5)
    errors := make(chan error, 1)
    done := make(chan bool, 1)
    
    // 数据生产者
    go func() {
        defer close(data)
        for i := 0; i < 10; i++ {
            if i == 7 {
                errors <- fmt.Errorf("处理第%d项时出错", i)
                return
            }
            data <- i
            time.Sleep(100 * time.Millisecond)
        }
        done <- true
    }()
    
    // 数据消费者
    for {
        select {
        case value, ok := <-data:
            if !ok {
                fmt.Println("数据通道已关闭")
                return
            }
            fmt.Printf("处理数据: %d\n", value)
            
        case err := <-errors:
            fmt.Printf("处理错误: %v\n", err)
            return
            
        case <-done:
            fmt.Println("处理完成")
            return
            
        case <-time.After(1 * time.Second):
            fmt.Println("处理超时")
            return
        }
    }
}

🎯 核心知识点总结

最佳实践要点

  1. 错误检查: 总是检查和处理错误
  2. 资源清理: 使用defer确保资源被清理
  3. 可读性: 保持控制流的清晰和简洁
  4. 并发安全: 在并发环境中正确使用select和通道

正在精进