50周学习go语言:第四周 函数与错误处理深度解析

发布于:2025-02-24 ⋅ 阅读:(13) ⋅ 点赞:(0)

第四周 函数与错误处理深度解析
以下是第4周函数基础的深度教程,包含两个完整案例和详细实现细节:


第四周:函数与错误处理深度解析


一、函数定义与参数传递

1. 基础函数结构
// 基本语法
func 函数名(参数列表) 返回值类型 {
    // 函数体
}

// 示例:计算圆面积
func circleArea(radius float64) float64 {
    return math.Pi * radius * radius
}
2. 参数传递方式
类型 语法 特点 适用场景
值传递 func(a int) 创建副本 小型数据
指针传递 func(a *int) 操作原值 大型结构体
引用类型 func(s []int) 共享底层数组 切片/map
3. 参数类型详解

案例1:值传递 vs 指针传递

// 值传递示例
func addValue(n int) {
    n += 10
}

// 指针传递示例
func addPointer(n *int) {
    *n += 10
}

func main() {
    num := 5
    addValue(num)      // 不影响原值
    addPointer(&num)   // 修改原值
    fmt.Println(num)   // 输出15
}

二、返回值与错误处理

1. 多返回值
// 返回商和余数
func div(a, b int) (int, int) {
    return a/b, a%b
}

// 使用
q, r := div(17, 5)  // q=3, r=2
2. 错误处理规范
// 标准错误返回格式
func sqrt(x float64) (float64, error) {
    if x < 0 {
        return 0, errors.New("负数不能求平方根")
    }
    return math.Sqrt(x), nil
}
3. 错误处理实践

案例2:安全除法函数

func safeDivide(a, b int) (int, error) {
    if b == 0 {
        return 0, fmt.Errorf("除数不能为零 (a=%d, b=%d)", a, b)
    }
    return a / b, nil
}

// 使用示例
result, err := safeDivide(10, 0)
if err != nil {
    log.Printf("计算失败: %v", err)
    // 输出:计算失败: 除数不能为零 (a=10, b=0)
}

三、素数判断任务实现

需求分析
  1. 函数isPrime接收整数参数
  2. 返回:
    • true:质数
    • false:非质数
    • error:输入非法(n < 2)
  3. 使用优化算法(试除法到平方根)
版本1:基础实现
func isPrime(n int) (bool, error) {
    if n < 2 {
        return false, fmt.Errorf("无效输入:%d 必须大于1", n)
    }
    
    // 单独处理2(唯一的偶质数)
    if n == 2 {
        return true, nil
    }
    
    // 排除偶数
    if n%2 == 0 {
        return false, nil
    }
    
    // 试除到平方根
    max := int(math.Sqrt(float64(n)))
    for i := 3; i <= max; i += 2 {
        if n%i == 0 {
            return false, nil
        }
    }
    return true, nil
}
版本2:性能优化(预计算小质数)
var smallPrimes = []int{2, 3, 5, 7, 11, 13}

func optimizedIsPrime(n int) (bool, error) {
    if n < 2 {
        return false, fmt.Errorf("invalid input: %d", n)
    }
    
    // 先检查小质数
    for _, p := range smallPrimes {
        if n == p {
            return true, nil
        }
        if n%p == 0 {
            return false, nil
        }
    }
    
    // 从17开始检查(大于预存小质数的下一个奇数)
    max := int(math.Sqrt(float64(n)))
    for i := 17; i <= max; i += 2 {
        if n%i == 0 {
            return false, nil
        }
    }
    return true, nil
}

四、测试驱动开发

1. 表格驱动测试
func TestIsPrime(t *testing.T) {
    tests := []struct {
        name    string
        input   int
        want    bool
        wantErr bool
    }{
        {"负数测试", -5, false, true},
        {"0测试", 0, false, true},
        {"1测试", 1, false, true},
        {"最小质数", 2, true, false},
        {"偶合数", 4, false, false},
        {"大质数", 9973, true, false},
        {"平方数", 25, false, false},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := isPrime(tt.input)
            if (err != nil) != tt.wantErr {
                t.Errorf("isPrime() error = %v, wantErr %v", err, tt.wantErr)
                return
            }
            if got != tt.want {
                t.Errorf("isPrime() = %v, want %v", got, tt.want)
            }
        })
    }
}
2. 性能基准测试
func BenchmarkIsPrime(b *testing.B) {
    for i := 0; i < b.N; i++ {
        isPrime(104729) // 第10000个质数
    }
}

五、错误处理进阶

1. 错误包装
func process(n int) error {
    if valid, err := isPrime(n); err != nil {
        return fmt.Errorf("处理失败: %w", err)  // 使用%w包装错误
    } else if !valid {
        return errors.New("需要质数输入")
    }
    return nil
}
2. 错误类型断言
func handleError(err error) {
    if pe, ok := err.(*PrimeError); ok {
        fmt.Printf("质数错误代码%d: %s\n", pe.Code, pe.Message)
    } else {
        fmt.Println("通用错误:", err)
    }
}

// 自定义错误类型
type PrimeError struct {
    Code    int
    Message string
}

func (e *PrimeError) Error() string {
    return fmt.Sprintf("错误%d: %s", e.Code, e.Message)
}

六、扩展练习

  1. 质因数分解函数

    func primeFactors(n int) ([]int, error) {
        // 返回所有质因数
        // 示例:输入12 → [2, 2, 3]
    }
    
  2. 并发素数筛选

    // 实现并发版本的埃拉托斯特尼筛法
    func sieve(max int) []int {
        // 返回所有小于max的质数
    }
    

七、学习检查清单

  • 能正确声明带多个返回值的函数
  • 会使用指针参数修改外部变量
  • 理解error接口的使用场景
  • 能编写表格驱动测试用例
  • 会处理负数输入的边界情况
  • 能解释试除法的优化原理
  • 理解自定义错误类型的优势

通过这两个完整案例(安全除法与素数判断),你已掌握Go函数的定义、参数传递、错误处理和测试方法。建议:

  1. 实际运行测试用例观察结果
  2. 使用go test -bench .查看性能差异
  3. 尝试为优化版本添加更多预计算质数
  4. 实现扩展练习中的质因数分解功能

以下同扩展部分的代码与分析

以下是两个扩展练习的完整实现代码,包含详细注释说明每个关键步骤:


扩展练习1:质因数分解函数

// primeFactors 返回给定整数的质因数分解结果
// 输入:n - 需要分解的正整数(n >= 2)
// 返回:质因数切片(升序排列)和可能的错误
func primeFactors(n int) ([]int, error) {
    // 输入验证
    if n < 2 {
        return nil, fmt.Errorf("无效输入:%d 必须大于1", n)
    }

    factors := make([]int, 0)
    original := n
    
    // 处理偶数因子
    for n%2 == 0 {
        factors = append(factors, 2)
        n /= 2
    }

    // 从3开始检查奇数因子(步长2)
    for i := 3; i*i <= n; i += 2 {
        // 当i是因子时持续除尽
        for n%i == 0 {
            factors = append(factors, i)
            n /= i
        }
    }

    // 处理剩余的大质数因子
    if n > 2 {
        factors = append(factors, n)
    }

    // 验证分解结果
    product := 1
    for _, f := range factors {
        product *= f
    }
    if product != original {
        return nil, fmt.Errorf("分解验证失败:%d != %d", product, original)
    }

    return factors, nil
}

/* 使用示例:
factors, err := primeFactors(360)
if err != nil {
    log.Fatal(err)
}
fmt.Println(factors) // 输出:[2 2 2 3 3 5]
*/

扩展练习2:并发素数筛选(埃拉托斯特尼筛法)

// sieve 使用并发筛法返回所有小于max的质数
func sieve(max int) []int {
    if max < 2 {
        return []int{}
    }

    // 创建初始数字生成器(2到max)
    ch := generateNumbers(2, max)
    
    primes := make([]int, 0)
    
    for {
        // 从当前通道获取候选质数
        p, ok := <-ch
        if !ok {
            break
        }
        
        primes = append(primes, p)
        
        // 创建新的过滤通道
        ch = filter(ch, p)
    }
    
    return primes
}

// generateNumbers 生成连续整数序列
func generateNumbers(start, end int) <-chan int {
    ch := make(chan int)
    go func() {
        for i := start; i <= end; i++ {
            ch <- i
        }
        close(ch)
    }()
    return ch
}

// filter 创建新的通道过滤特定质数的倍数
func filter(in <-chan int, prime int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            // 过滤掉能被当前质数整除的数
            if n%prime != 0 {
                out <- n
            }
        }
        close(out)
    }()
    return out
}

/* 使用示例:
primes := sieve(30)
fmt.Println(primes) // 输出:[2 3 5 7 11 13 17 19 23 29]
*/

代码关键点解析

质因数分解函数
  1. 输入验证:确保输入符合质因数分解的基本要求
  2. 分阶段处理
    • 先处理所有偶数因子(2的幂次)
    • 再处理奇数因子(从3开始,步长2)
  3. 验证机制:最终乘积验证确保分解的正确性
  4. 性能优化
    • 循环上限设置为√n(i*i <= n)
    • 跳过偶数检查(通过步长2)
并发素数筛法
  1. 通道链式结构
    generateNumbers
    filter2
    filter3
    filter5
    ...
  2. 动态过滤
    • 每个新发现的质数都会创建一个新的过滤通道
    • 前序通道处理完成后自动关闭
  3. 并发控制
    • 每个filter操作在独立的goroutine中执行
    • 通过通道关闭机制自然终止循环

测试用例

质因数分解测试
func TestPrimeFactors(t *testing.T) {
    tests := []struct {
        input  int
        expect []int
    }{
        {12, []int{2, 2, 3}},
        {360, []int{2, 2, 2, 3, 3, 5}},
        {9973, []int{9973}},      // 大质数
        {1024, []int{2, 2, 2, 2, 2, 2, 2, 2, 2, 2}},
    }

    for _, tt := range tests {
        result, _ := primeFactors(tt.input)
        if !reflect.DeepEqual(result, tt.expect) {
            t.Errorf("输入%d 期望%v 实际%v", tt.input, tt.expect, result)
        }
    }
}
并发筛法测试
func TestSieve(t *testing.T) {
    expected := []int{2, 3, 5, 7, 11, 13, 17, 19, 23, 29}
    result := sieve(30)
    if !reflect.DeepEqual(result, expected) {
        t.Errorf("期望%v 实际%v", expected, result)
    }
}

性能对比

实现方式 时间复杂度 空间复杂度 适合场景
传统筛法 O(n log log n) O(n) 大数据量
并发筛法 O(n)(理论) O(k)(k为goroutine数量) 学习并发原理

实际测试显示,当max超过1,000,000时,传统实现效率更高。并发版本更适合理解通道和goroutine的协作机制。


扩展方向

  1. 质因数分解优化

    • 预生成小质数列表加速试除过程
    • 添加Pollard’s Rho算法处理大数分解
  2. 并发筛法改进

    • 限制最大goroutine数量
    • 添加超时控制
    • 实现分片并行处理

这两个练习展示了Go语言在处理数学问题和并发编程方面的优势,建议通过实际调试观察通道数据的流动过程,可以更深入理解并发筛法的工作原理。


网站公告

今日签到

点亮在社区的每一天
去签到