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

news/2025/2/24 11:56:37

第四周 函数与错误处理深度解析
以下是第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语言在处理数学问题和并发编程方面的优势,建议通过实际调试观察通道数据的流动过程,可以更深入理解并发筛法的工作原理。


http://www.niftyadmin.cn/n/5864300.html

相关文章

Linux 命令大全完整版(13)

5.文件管理命令 patch 功能说明&#xff1a;修补文件。语  法&#xff1a;patch [-bceEflnNRstTuvZ][-B <备份字首字符串>][-d <工作目录>][-D <标示符号>][-F <监别列数>][-g <控制数值>][-i <修补文件>][-o <输出文件>][-p &l…

【大模型系列】使用docker安装向量数据库Milvus问题备忘

在大模型应用开发过程中&#xff0c;向量数据库是一个重要的知识点&#xff0c;在应用中扮演重要角色&#xff1b;以下介绍向量数据库 milvus Milvus 中文文档地址 https://milvus.io/docs/zh 使用docker-compose 安装standalone模式的milvus时可能会出现异常 因为默认会从国…

Qt | Excel创建、打开、读写、另存和关闭

01 如何在Qt中使用QXlsx库进行Excel文件的读写操作,包括创建新Excel、写入数据、读取数据以及文件保存和释放资源。通过实例展示了如何加载库、编写.h和.cpp文件,并演示了使用单元格引用和行列号进行数据操作的方法。 QXlsx是一个可以读写Excel文件的库。不依赖office以及…

Windows ARM工控主板支持EC200A系列4G模块

EC200A 系列是移远通信专为 M2M 和 IoT 领域设计的 LTE Cat 4 无线通信模块&#xff0c;支持最大下行速率 150 Mbps 和最大上行速率 50 Mbps。本文将介绍如何在英创推出的名片尺寸Windows 10 ARM64工控主板ESM8400上使用EC200A 4G模块。 1. 硬件连接 EC200A可直接插入ESM8400评…

深度学习-127-LangGraph之基础知识(四)自定义状态添加额外字段的聊天机器人

文章目录 1 自定义状态2 自定义工具2.1 完善工具human_assistance2.2 浏览器工具baidu_search3 聊天机器人3.1 绑定工具的聊天模型3.2 聊天机器人(带记忆)4 调用图4.1 调用工具时中断4.2 人工提供信息恢复4.3 查询存储的状态4.4 手动更新状态5 参考附录使用LangGraph,在状态中…

精选案例展 | 智己汽车—全栈可观测驱动智能化运营与成本优化

本案例为“观测先锋 2024 可观测平台创新应用案例大赛”精选案例&#xff0c;同时荣获IT168“2024技术卓越奖评选-年度创新解决方案”奖。 项目背景 近年来&#xff0c;中国汽车行业进入转型升级阶段&#xff0c;智能网联技术成为行业发展的核心。车联网、自动驾驶等技术的加…

每日一题——主持人调度(二)

主持人调度&#xff08;二&#xff09; 问题描述输入格式输出格式示例示例 1&#xff1a;示例 2&#xff1a; 第一种直观解法代码逻辑详解贪心算法的关键点复杂度分析 第二种思路代码解析完整代码与注释算法逻辑总结复杂度分析示例验证输入数据代码执行过程1. 提取并排序开始时…

MQTT实现智能家居------2、写MQTT程序的思路

举个最简单的例子&#xff1a; 手机------服务器-------家具 我们这里只看手机和家具的客户端&#xff1a; 手机&#xff1a;1&#xff09;需要连接服务器 2&#xff09;需要发布指令给服务器到家里的家具 3&#xff09;接受来自于家里家具的异常状况 4&#xff09;保持心…