结对编程——数学运算练习系统
一、实践背景
我和搭档(学号:2352222)决定一起开发一个“数学运算练习系统”,旨在帮助小学生巩固四则运算能力。我们采用了结对编程的方式,一人担任负责写代码,另一人担任负责审查代码和提供思路,每隔一段时间轮换角色。
二、设计思路
系统的核心功能是随机生成包含三个数字和两个运算符的数学题目,并支持加减乘除运算。考虑到小学生的接受程度,我们设定了以下规则:
1.运算优先级:遵循“先乘除后加减”的原则。
2.结果范围:限制题目结果在0到1000之间,避免过大或负数的答案。
3.小数处理:如果遇到除不尽的情况,要求用户输入保留两位小数的结果。
同时,为了提高系统的竞争力和实用性,我们在核心功能的基础上,进一步优化了用户体验,并增加了以下额外功能:
-
题目分组与进度管理
每周300道题,分为30组:每组包含10道题目,用户可以根据自身时间安排,分批次完成练习。
进度保存:系统通过progress.txt文件记录用户已完成的组数和当前总分,即使中途退出,下次启动时仍可继续练习。
实时进度显示:在用户选择菜单中,动态显示当前完成的组数(如当前进度: 5/30组)。 -
智能评估与反馈机制
即时评分:每完成一组题目,系统会统计正确率并显示得分(如本组得分: 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.退出界面(退出后,系统会显示最终总得分)

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