结对编程——数学运算练习系统

一、实践背景
我和搭档(学号:2352222)决定一起开发一个“数学运算练习系统”,旨在帮助小学生巩固四则运算能力。我们采用了结对编程的方式,一人担任负责写代码,另一人担任负责审查代码和提供思路,每隔一段时间轮换角色。

二、设计思路
系统的核心功能是随机生成包含三个数字和两个运算符的数学题目,并支持加减乘除运算。考虑到小学生的接受程度,我们设定了以下规则:
1.运算优先级:遵循“先乘除后加减”的原则。
2.结果范围:限制题目结果在0到1000之间,避免过大或负数的答案。
3.小数处理:如果遇到除不尽的情况,要求用户输入保留两位小数的结果。
同时,为了提高系统的竞争力和实用性,我们在核心功能的基础上,进一步优化了用户体验,并增加了以下额外功能:

  1. 题目分组与进度管理
    每周300道题,分为30组:每组包含10道题目,用户可以根据自身时间安排,分批次完成练习。
    进度保存:系统通过progress.txt文件记录用户已完成的组数和当前总分,即使中途退出,下次启动时仍可继续练习。
    实时进度显示:在用户选择菜单中,动态显示当前完成的组数(如当前进度: 5/30组)。

  2. 智能评估与反馈机制
    即时评分:每完成一组题目,系统会统计正确率并显示得分(如本组得分: 8/10)。
    总分统计:在菜单中提供“查看当前总分”选项,汇总所有已完成的题目得分,并计算正确率百分比(如当前总分: 75/100 (75.0%))。
    错题提示:对于回答错误的题目,系统会直接显示正确答案,并区分整数和小数结果(如错误!正确答案是: 12.34 或 错误!正确答案是: 15)。

3.题目生成优化
动态难度控制:通过isValidExpression函数过滤无效题目(如除数为零或结果超范围),确保所有题目符合小学生的能力范围(0 ≤ 结果 ≤ 1000)。
运算符随机组合:支持加减乘除的任意两两组合,并严格遵循“先乘除后加减”的优先级规则,避免生成歧义表达式(如1 + 2 * 3始终按1 + (2 * 3)计算)。
小数处理提示:若题目结果为非整数,系统会主动提示用户保留两位小数(如提示:本题结果需要保留两位小数),培养规范答题习惯。

三、合作过程
1.函数拆分与分工
我们首先将系统拆分为多个功能模块:
getRandomNumber:生成随机数。

点击查看代码
int getRandomNumber(int min, int max) {
    return rand() % (max - min + 1) + min;
}

isValidExpression:验证表达式是否合法。

点击查看代码
bool isValidExpression(int num1, int num2, int num3, char op1, char op2) {
    double temp;
    
    // 根据运算符优先级计算
    if ((op2 == '*' || op2 == '/') && (op1 == '+' || op1 == '-')) {
        // 先计算第二个运算
        switch(op2) {
            case '*': 
                temp = num2 * num3; 
                break;
            case '/': 
                if (num3 == 0) return false;
                temp = (double)num2 / num3; 
                break;
            default: 
                return false;
        }
        
        // 再计算第一个运算
        switch(op1) {
            case '+': 
                temp = num1 + temp; 
                break;
            case '-': 
                temp = num1 - temp; 
                break;
            default: 
                return false;
        }
    } else {
        // 从左到右计算
        // 第一个运算
        switch(op1) {
            case '+': 
                temp = num1 + num2; 
                break;
            case '-': 
                temp = num1 - num2; 
                break;
            case '*': 
                temp = num1 * num2; 
                break;
            case '/': 
                if (num2 == 0) return false;
                temp = (double)num1 / num2; 
                break;
            default: 
                return false;
        }
        
        // 第二个运算
        switch(op2) {
            case '+': 
                temp += num3; 
                break;
            case '-': 
                temp -= num3; 
                break;
            case '*': 
                temp *= num3; 
                break;
            case '/': 
                if (num3 == 0) return false;
                temp /= num3; 
                break;
            default: 
                return false;
        }
    }
    
    return temp >= 0 && temp <= 1000;
}

calculateResult:计算表达式结果。

点击查看代码
double calculateResult(int num1, int num2, int num3, char op1, char op2, bool *hasDecimal) {
    double result1, result2;
    *hasDecimal = false;
    
    // 根据运算符优先级计算
    if ((op2 == '*' || op2 == '/') && (op1 == '+' || op1 == '-')) {
        // 先计算第二个运算(乘除优先级高)
        switch(op2) {
            case '*': 
                result2 = num2 * num3; 
                break;
            case '/': 
                if (num3 == 0) return 0;
                result2 = (double)num2 / num3;
                if (num2 % num3 != 0) *hasDecimal = true;
                break;
            default: 
                result2 = 0;
        }
        
        // 再计算第一个运算
        switch(op1) {
            case '+': 
                result1 = num1 + result2; 
                break;
            case '-': 
                result1 = num1 - result2; 
                break;
            default: 
                result1 = 0;
        }
    } else {
        // 从左到右计算
        // 第一个运算
        switch(op1) {
            case '+': 
                result1 = num1 + num2; 
                break;
            case '-': 
                result1 = num1 - num2; 
                break;
            case '*': 
                result1 = num1 * num2; 
                break;
            case '/': 
                if (num2 == 0) return 0;
                result1 = (double)num1 / num2;
                if (num1 % num2 != 0) *hasDecimal = true;
                break;
            default: 
                result1 = 0;
        }
        
        // 第二个运算
        switch(op2) {
            case '+': 
                result1 += num3; 
                break;
            case '-': 
                result1 -= num3; 
                break;
            case '*': 
                result1 *= num3; 
                break;
            case '/': 
                if (num3 == 0) return 0;
                if (fmod(result1, num3) != 0) *hasDecimal = true;
                result1 /= num3;
                break;
            default: 
                result1 = 0;
        }
    }
    
    return result1;
}

completeOneSet:完成一组10道题目。

点击查看代码
int completeOneSet(int setNumber) {
    int score = 0;
    char operators[] = {'+', '-', '*', '/'};
    int i;
    
    printf("\n=== 第%d组题目 ===\n", setNumber);
    
    for (i = 1; i <= 10; i++) {
        char op1, op2;
        int num1, num2, num3;
        bool valid = false;
        
        // 生成有效的表达式
        while (!valid) {
            op1 = operators[getRandomNumber(0, 3)];
            op2 = operators[getRandomNumber(0, 3)];
            
            num1 = getRandomNumber(1, 100);
            num2 = getRandomNumber(1, 100);
            num3 = getRandomNumber(1, 100);
            
            valid = isValidExpression(num1, num2, num3, op1, op2);
            
            if (op1 == '/' && num2 == 0) valid = false;
            if (op2 == '/' && num3 == 0) valid = false;
        }
        
        printf("题目%d: %d %c %d %c %d = ", i, num1, op1, num2, op2, num3);
        
        bool hasDecimal;
        double correctAnswer = calculateResult(num1, num2, num3, op1, op2, &hasDecimal);
        
        if (hasDecimal) {
            printf("\n提示:本题结果需要保留两位小数\n请输入答案: ");
        }
        
        double userAnswer;
        scanf("%lf", &userAnswer);
        
        if (!isAnswerValid(userAnswer)) {
            printf("错误!答案必须在0-1000之间。\n");
            continue;
        }
        
        if (fabs(userAnswer - correctAnswer) < 0.01) {
            printf("正确!\n");
            score++;
        } else {
            if (hasDecimal) {
                printf("错误!正确答案是: %.2f\n", correctAnswer);
            } else {
                printf("错误!正确答案是: %d\n", (int)correctAnswer);
            }
        }
    }
    
    printf("\n本组得分: %d/10\n", score);
    return score;
}

我负责编写表达式验证和计算逻辑,搭档则专注于用户交互和进度管理部分。

2.运算符优先级的实现
在实现calculateResult函数时,我们遇到了运算符优先级的问题。最初的版本没有考虑优先级,导致“1 + 2 * 3”的结果被计算为9而非7。通过多次讨论和调试,我们引入了条件判断,优先处理乘除运算,从而解决了这一问题。

3.边界条件的测试
搭档在审查代码时提出了许多边界情况,比如除数为零或结果为负数。我们通过isValidExpression函数对生成的题目进行过滤,确保所有题目都符合要求。此外,还添加了小数结果的提示功能。

4.进度保存功能
我提议增加进度保存功能,将完成的组数和总分记录到文件中。我们通过简单的文件操作实现了这一功能,使得用户下次启动程序时可以继续之前的练习。

四、程序源码

点击查看代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdbool.h>
#include <math.h>

// 生成指定范围内的随机数
int getRandomNumber(int min, int max) {
    return rand() % (max - min + 1) + min;
}

// 检查答案是否在有效范围内
bool isAnswerValid(double answer) {
    return answer >= 0 && answer <= 1000;
}

// 根据运算符优先级计算表达式结果
double calculateResult(int num1, int num2, int num3, char op1, char op2, bool *hasDecimal) {
    double result1, result2;
    *hasDecimal = false;
    
    // 根据运算符优先级计算
    if ((op2 == '*' || op2 == '/') && (op1 == '+' || op1 == '-')) {
        // 先计算第二个运算(乘除优先级高)
        switch(op2) {
            case '*': 
                result2 = num2 * num3; 
                break;
            case '/': 
                if (num3 == 0) return 0;
                result2 = (double)num2 / num3;
                if (num2 % num3 != 0) *hasDecimal = true;
                break;
            default: 
                result2 = 0;
        }
        
        // 再计算第一个运算
        switch(op1) {
            case '+': 
                result1 = num1 + result2; 
                break;
            case '-': 
                result1 = num1 - result2; 
                break;
            default: 
                result1 = 0;
        }
    } else {
        // 从左到右计算
        // 第一个运算
        switch(op1) {
            case '+': 
                result1 = num1 + num2; 
                break;
            case '-': 
                result1 = num1 - num2; 
                break;
            case '*': 
                result1 = num1 * num2; 
                break;
            case '/': 
                if (num2 == 0) return 0;
                result1 = (double)num1 / num2;
                if (num1 % num2 != 0) *hasDecimal = true;
                break;
            default: 
                result1 = 0;
        }
        
        // 第二个运算
        switch(op2) {
            case '+': 
                result1 += num3; 
                break;
            case '-': 
                result1 -= num3; 
                break;
            case '*': 
                result1 *= num3; 
                break;
            case '/': 
                if (num3 == 0) return 0;
                if (fmod(result1, num3) != 0) *hasDecimal = true;
                result1 /= num3;
                break;
            default: 
                result1 = 0;
        }
    }
    
    return result1;
}

// 确保表达式结果在有效范围内
bool isValidExpression(int num1, int num2, int num3, char op1, char op2) {
    double temp;
    
    // 根据运算符优先级计算
    if ((op2 == '*' || op2 == '/') && (op1 == '+' || op1 == '-')) {
        // 先计算第二个运算
        switch(op2) {
            case '*': 
                temp = num2 * num3; 
                break;
            case '/': 
                if (num3 == 0) return false;
                temp = (double)num2 / num3; 
                break;
            default: 
                return false;
        }
        
        // 再计算第一个运算
        switch(op1) {
            case '+': 
                temp = num1 + temp; 
                break;
            case '-': 
                temp = num1 - temp; 
                break;
            default: 
                return false;
        }
    } else {
        // 从左到右计算
        // 第一个运算
        switch(op1) {
            case '+': 
                temp = num1 + num2; 
                break;
            case '-': 
                temp = num1 - num2; 
                break;
            case '*': 
                temp = num1 * num2; 
                break;
            case '/': 
                if (num2 == 0) return false;
                temp = (double)num1 / num2; 
                break;
            default: 
                return false;
        }
        
        // 第二个运算
        switch(op2) {
            case '+': 
                temp += num3; 
                break;
            case '-': 
                temp -= num3; 
                break;
            case '*': 
                temp *= num3; 
                break;
            case '/': 
                if (num3 == 0) return false;
                temp /= num3; 
                break;
            default: 
                return false;
        }
    }
    
    return temp >= 0 && temp <= 1000;
}

// 完成一组10道题
int completeOneSet(int setNumber) {
    int score = 0;
    char operators[] = {'+', '-', '*', '/'};
    int i;
    
    printf("\n=== 第%d组题目 ===\n", setNumber);
    
    for (i = 1; i <= 10; i++) {
        char op1, op2;
        int num1, num2, num3;
        bool valid = false;
        
        // 生成有效的表达式
        while (!valid) {
            op1 = operators[getRandomNumber(0, 3)];
            op2 = operators[getRandomNumber(0, 3)];
            
            num1 = getRandomNumber(1, 100);
            num2 = getRandomNumber(1, 100);
            num3 = getRandomNumber(1, 100);
            
            valid = isValidExpression(num1, num2, num3, op1, op2);
            
            if (op1 == '/' && num2 == 0) valid = false;
            if (op2 == '/' && num3 == 0) valid = false;
        }
        
        printf("题目%d: %d %c %d %c %d = ", i, num1, op1, num2, op2, num3);
        
        bool hasDecimal;
        double correctAnswer = calculateResult(num1, num2, num3, op1, op2, &hasDecimal);
        
        if (hasDecimal) {
            printf("\n提示:本题结果需要保留两位小数\n请输入答案: ");
        }
        
        double userAnswer;
        scanf("%lf", &userAnswer);
        
        if (!isAnswerValid(userAnswer)) {
            printf("错误!答案必须在0-1000之间。\n");
            continue;
        }
        
        if (fabs(userAnswer - correctAnswer) < 0.01) {
            printf("正确!\n");
            score++;
        } else {
            if (hasDecimal) {
                printf("错误!正确答案是: %.2f\n", correctAnswer);
            } else {
                printf("错误!正确答案是: %d\n", (int)correctAnswer);
            }
        }
    }
    
    printf("\n本组得分: %d/10\n", score);
    return score;
}

int main() {
    srand(time(0));
    
    printf("数学运算练习系统\n");
    printf("每周300道题(30组,每组10题)\n");
    printf("运算遵循先乘除后加减的优先级规则\n");
    printf("如果遇到除不尽的情况,请保留两位小数\n\n");
    
    int totalScore = 0;
    int setsCompleted = 0;
    const int totalSets = 30; // 每周30组
    
    while (setsCompleted < totalSets) {
        printf("\n当前进度: %d/%d组\n", setsCompleted, totalSets);
        printf("1. 开始下一组\n");
        printf("2. 查看当前总分\n");
        printf("3. 退出\n");
        printf("请选择: ");
        
        int choice;
        scanf("%d", &choice);
        
        switch(choice) {
            case 1: {
                int setScore = completeOneSet(setsCompleted + 1);
                totalScore += setScore;
                setsCompleted++;
                
                // 保存进度
                FILE *file = fopen("progress.txt", "w");
                if (file) {
                    fprintf(file, "%d %d", setsCompleted, totalScore);
                    fclose(file);
                }
                break;
            }
            case 2:
                printf("\n当前总分: %d/%d (%.1f%%)\n", 
                      totalScore, setsCompleted * 10,
                      (double)totalScore / (setsCompleted * 10) * 100);
                break;
            case 3:
                printf("\n练习结束!最终得分: %d/%d\n", totalScore, totalSets * 10);
                return 0;
            default:
                printf("无效选择,请重新输入\n");
        }
    }
    
    printf("\n恭喜完成本周所有练习!\n");
    printf("最终得分: %d/%d (%.1f%%)\n", 
          totalScore, totalSets * 10,
          (double)totalScore / (totalSets * 10) * 100);
    
    return 0;
}

五、功能界面
1.初始界面

2.做题界面(系统会标明练习组数,自动评判输入答案是否正确,并对错误答案给出正确数据;每完成一组题目后,系统会给出用户的答题正确率)

3.查看总分界面

4.退出界面(退出后,系统会显示最终总得分)

六、个人心得
我的心得:在这次数学运算练习系统的开发中,我深刻体会到结对编程的价值。与搭档“编写代码”“审查代码”角色任务的转换,我们实现了优势互补:我专注算法逻辑,搭档优化用户体验,这种分工让系统既保证了运算准确性,又提升了系统交互性。在开发过程中,我们遇到了运算符优先级处理、边界条件判断等技术难题。通过实时讨论和互相审查,我们能够快速发现潜在问题,比如最初忽略的运算顺序错误,在搭档的及时提醒下得到了修正。这种即时反馈机制大大减少了后期调试的时间成本。结对编程不仅提高了代码质量,更培养了我们的协作能力。在功能设计上,我们有过不同见解,比如题目分组方式的讨论,但通过用户场景模拟和理性分析,最终达成共识。事实上,正是这种思维碰撞让系统设计更加完善。
搭档的心得:结对编程是一种高效的协作开发方式。作为代码编写人员时,专注代码实现的同时能即时获得同伴的反馈;作为观察者时,更能全局审视逻辑漏洞,提出优化建议。双人轮流操作不仅减少了低级错误,还促进了技术思路的碰撞,往往能催生更高质的解决方案。在实时讨论中快速掌握对方的技术特长,互补短板。这样的实践对培养沟通能力和耐心都大有裨益。

posted @ 2025-04-15 21:32  拾光匣子  阅读(49)  评论(0)    收藏  举报