这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13479 |
这个作业的目标 | 实现一个自动生成小学四则运算题目的命令行程序 |
项目成员:莫圣韬 戴宏翔
1.github链接
https://gitee.com/elysiak/Paired-project
2.psp表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 40 | 50 |
Estimate | 估计这个任务需要多少时间 | 10 | 15 |
Requirements Analysis | 需求分析 | 30 | 35 |
Development | 开发 | 590 | 580 |
Analysis | 需求分析(包括学习新技术) | 70 | 100 |
Design Spec | 生成设计文档 | 40 | 35 |
Design Review | 设计复审 | 30 | 25 |
Coding Standard | 代码规范制定 | 20 | 15 |
Architecture Design | 架构设计 | 60 | 70 |
Detailed Design | 详细设计 | 40 | 45 |
Coding | 具体编码 | 180 | 200 |
Code Review | 代码复审 | 40 | 30 |
Unit Test | 单元测试 | 30 | 20 |
Integration Test | 集成测试 | 30 | 20 |
System Test | 系统测试 | 30 | 20 |
Reporting | 报告 | 80 | 135 |
Test Report | 测试报告 | 40 | 45 |
Performance Analysis | 性能分析报告 | 30 | 35 |
Size Measurement | 计算工作量 | 10 | 15 |
Postmortem & Process Improvement Plan | 事后总结,并提交过程改进计划 | 30 | 40 |
Total | 合计 | 710 | 765 |
3.效能分析
由图可以看出性能瓶颈是exression类中的evaluate函数
原来的问题:
- 在乘除法处理阶段,每次删除操作都会导致向量中元素的移动,这在表达式较长时会造成较大的性能开销。
- 循环中嵌套了删除操作,导致最坏情况下时间复杂度为O(n^2)。
优化后
优化思路
使用两个栈:一个存储操作数(numStack),一个存储运算符(opStack)。
遍历表达式中的每个元素(操作数和运算符):
- 遇到操作数,压入操作数栈。
- 遇到运算符,与运算符栈顶的运算符比较优先级:
如果栈顶运算符优先级不低于当前运算符,则从操作数栈弹出两个操作数,从运算符栈弹出一个运算符,进行计算,并将结果压入操作数栈。重复此过程直到栈顶运算符优先级低于当前运算符或 栈为空。然后将当前运算符压入运算符栈。遍历结束后,将运算符栈中剩余的运算符依次弹出并计算,直到运算符栈为空。
操作数栈中剩下的最后一个数就是表达式的结果。
4.设计实现过程
类
类1: Fraction (分数类)
功能:表示和操作分数
方法:
- 构造函数(整数、字符串)
- 算术运算(+、-、×、÷)
- 比较运算(==、<、>、<=、>=、!=)
- 简化分数
- 字符串转换
类2: Expression (表达式类)
功能:表示和计算数学表达式
核心方法:
- evaluate():表达式求值(双栈算法)
- isValid():表达式有效性检查
- toString():表达式字符串表示
类3: ProblemGenerator (题目生成器)
职责:生成随机的有效表达式
核心方法:
- generate():生成随机表达式
- generateNumber():生成随机数
- generateOperator():生成随机运算符
类4: Application (应用程序)
功能:协调整个程序流程
核心方法:
- generateProblems():生成题目文件
- checkAnswers():检查答案
- parseExpression():解析表达式字符串
- run():主运行逻辑
关键函数
Expression::evaluate()
ProblemGenerator::generate()
Application::generateProblems()
Application::checkAnswers()
5.代码说明
Fraction
点击查看代码
class Fraction {
private:int numerator; // 分子int denominator; // 分母// 分数简化函数,将分数约分到最简形式// 通过计算分子和分母的最大公约数,将分数化为最简形式// 如果分子为0,则将分母设为1,表示0值分数void simplify() {if (numerator == 0) {denominator = 1; // 0值分数统一表示为0/1return;}// 计算分子和分母的最大公约数,注意分子取绝对值int gcd_val = gcd(std::abs(numerator), denominator);numerator /= gcd_val; // 分子约分denominator /= gcd_val; // 分母约分}// 计算两个整数的最大公约数(GCD)// 使用欧几里得算法递归计算最大公约数int gcd(int a, int b) {return b == 0 ? a : gcd(b, a % b);}public:// 构造函数,通过分子分母创建分数// 自动处理分母为0或负数的情况,并自动简化分数Fraction(int num = 0, int den = 1) : numerator(num), denominator(den) {if (denominator == 0) denominator = 1; // 防止除零错误,分母设为1if (denominator < 0) {// 确保分母始终为正,负号转移到分子numerator = -numerator;denominator = -denominator;}simplify(); // 构造完成后自动简化分数}// 字符串构造函数,支持多种分数格式// 自动识别并解析带分数、真分数和整数格式Fraction(const std::string& str) {if (str.find('\'') != std::string::npos) {// 处理带分数格式:a'b/c (如 "2'1/2" 表示 2又1/2)size_t pos1 = str.find('\''); // 定位单引号位置size_t pos2 = str.find('/'); // 定位斜杠位置int integer = std::stoi(str.substr(0, pos1)); // 提取整数部分int num = std::stoi(str.substr(pos1 + 1, pos2 - pos1 - 1)); // 提取分子int den = std::stoi(str.substr(pos2 + 1)); // 提取分母// 将带分数转换为假分数:a'b/c = (a×c + b)/cnumerator = integer * den + (integer >= 0 ? num : -num);denominator = den;}else if (str.find('/') != std::string::npos) {// 处理真分数格式:a/b (如 "3/4")size_t pos = str.find('/');numerator = std::stoi(str.substr(0, pos)); // 提取分子denominator = std::stoi(str.substr(pos + 1)); // 提取分母}else {// 处理整数格式 (如 "5")numerator = std::stoi(str);denominator = 1; // 整数分母为1}simplify(); // 解析完成后自动简化}// 将分数转换为字符串表示// 根据分数的值自动选择最合适的显示格式std::string toString() const {if (numerator == 0) {return "0"; // 0值分数直接返回"0"}if (denominator == 1) {return std::to_string(numerator); // 整数直接返回数字字符串}int integer = numerator / denominator; // 整数部分int remainder = std::abs(numerator) % denominator; // 余数部分(取绝对值)if (integer == 0) {// 真分数格式:分子/分母 (如 "3/4")return std::to_string(numerator) + "/" + std::to_string(denominator);}else if (remainder == 0) {// 整数格式 (如 "2")return std::to_string(integer);}else {// 带分数格式:整数'分子/分母 (如 "2'1/2")return std::to_string(integer) + "'" + std::to_string(remainder) + "/" + std::to_string(denominator);}}// 分数加法运算符重载// 通过通分后分子相加实现分数加法Fraction operator+(const Fraction& other) const {int num = numerator * other.denominator + other.numerator * denominator;int den = denominator * other.denominator;return Fraction(num, den); // 返回新分数,会自动简化}// 分数减法运算符重载// 通过通分后分子相减实现分数减法Fraction operator-(const Fraction& other) const {int num = numerator * other.denominator - other.numerator * denominator;int den = denominator * other.denominator;return Fraction(num, den);}// 分数乘法运算符重载// 分子乘分子,分母乘分母实现分数乘法Fraction operator*(const Fraction& other) const {int num = numerator * other.numerator;int den = denominator * other.denominator;return Fraction(num, den);}// 分数除法运算符重载// 通过乘以倒数实现分数除法,有除零保护Fraction operator/(const Fraction& other) const {if (other.numerator == 0) {return Fraction(0, 1); // 除零保护,返回0}// 分数除法:a/b ÷ c/d = a/b × d/cint num = numerator * other.denominator;int den = denominator * other.numerator;return Fraction(num, den);}// 分数相等比较运算符// 比较化简后的分子和分母是否完全相同bool operator==(const Fraction& other) const {return numerator == other.numerator && denominator == other.denominator;}// 分数小于比较运算符// 通过交叉相乘比较分数大小,避免浮点数精度问题bool operator<(const Fraction& other) const {return numerator * other.denominator < other.numerator * denominator;}// 分数大于比较运算符// 基于小于运算符实现:a > b 等价于 b < abool operator>(const Fraction& other) const {return other < *this;}// 分数小于等于比较运算符// 基于小于运算符实现:a <= b 等价于 !(b < a)bool operator<=(const Fraction& other) const {return !(other < *this);}// 分数大于等于比较运算符// 基于小于运算符实现:a >= b 等价于 !(a < b)bool operator>=(const Fraction& other) const {return !(*this < other);}// 分数不等于比较运算符// 基于等于运算符实现:a != b 等价于 !(a == b)bool operator!=(const Fraction& other) const {return !(*this == other);}// 检查分数是否为真分数// 真分数定义:|分子| < 分母bool isProper() const {return std::abs(numerator) < denominator;}// 检查分数是否为非负数// 仅检查分子,因为分母始终为正bool isNonNegative() const {return numerator >= 0;}// 检查分数是否为零// 检查分子是否为0bool isZero() const {return numerator == 0;}
};
Expression
点击查看代码
class Expression {
private:std::vector<Fraction> numbers; // 存储表达式中的所有操作数std::vector<std::string> operators; // 存储表达式中的所有运算符// 缓存机制:存储已计算的结果,避免重复计算mutable Fraction cachedResult; // 缓存的计算结果mutable bool isResultCached; // 标记结果是否已缓存// 预分配的栈空间:用于双栈算法,避免重复内存分配mutable std::vector<Fraction> numStack; // 操作数栈mutable std::vector<std::string> opStack; // 运算符栈// 获取运算符的优先级// 返回运算符的优先级数值,数值越大优先级越高int getPrecedence(const std::string& op) const {if (op == "*" || op == "/") return 2; // 乘除法优先级最高if (op == "+" || op == "-") return 1; // 加减法优先级较低return 0; // 其他运算符默认优先级}// 应用具体的运算操作// 根据运算符类型执行相应的分数运算Fraction applyOperation(const Fraction& left, const Fraction& right, const std::string& op) const {if (op == "+") return left + right; // 加法运算if (op == "-") return left - right; // 减法运算if (op == "*") return left * right; // 乘法运算if (op == "/") {if (right.isZero()) return Fraction(0, 1); // 除零保护,返回0return left / right; // 除法运算}return Fraction(0, 1); // 未知运算符返回0}// 表达式求值函数,使用双栈算法// 实现运算符优先级处理,确保乘除法先于加减法执行Fraction evaluate() const {// 如果已经计算过结果,直接返回缓存的值,避免重复计算if (isResultCached) {return cachedResult;}// 处理空表达式的情况if (numbers.empty()) {cachedResult = Fraction(0, 1);isResultCached = true;return cachedResult;}// 清空栈空间但保留预分配的容量,避免重复内存分配numStack.clear();opStack.clear();// 第一个操作数直接压入操作数栈numStack.push_back(numbers[0]);// 遍历所有运算符和对应的操作数for (size_t i = 0; i < operators.size(); i++) {const std::string& currentOp = operators[i]; // 当前运算符const Fraction& currentNum = numbers[i + 1]; // 当前操作数// 处理运算符优先级:当栈顶运算符优先级不低于当前运算符时,先计算栈顶运算// 这确保了乘除法在加减法之前执行while (!opStack.empty() &&getPrecedence(opStack.back()) >= getPrecedence(currentOp)) {// 从栈中弹出运算符和操作数进行计算Fraction right = numStack.back(); numStack.pop_back();Fraction left = numStack.back(); numStack.pop_back();std::string op = opStack.back(); opStack.pop_back();// 执行运算并将结果压回操作数栈Fraction result = applyOperation(left, right, op);numStack.push_back(result);}// 当前运算符和操作数入栈,等待后续处理opStack.push_back(currentOp);numStack.push_back(currentNum);}// 处理栈中剩余的运算(从左到右顺序)while (!opStack.empty()) {Fraction right = numStack.back(); numStack.pop_back();Fraction left = numStack.back(); numStack.pop_back();std::string op = opStack.back(); opStack.pop_back();Fraction result = applyOperation(left, right, op);numStack.push_back(result);}// 栈顶元素即为最终计算结果,存入缓存cachedResult = numStack.back();isResultCached = true;return cachedResult;}// 获取运算符的显示形式// 将内部运算符转换为用户友好的显示形式std::string getDisplayOperator(const std::string& op) const {if (op == "*") return "×"; // 乘法显示为×if (op == "/") return "÷"; // 除法显示为÷return op; // 加减法保持原样}public:// 构造函数:使用操作数和运算符列表初始化表达式// 同时预分配栈空间以提高性能Expression(const std::vector<Fraction>& nums, const std::vector<std::string>& ops): numbers(nums), operators(ops), isResultCached(false) {// 预分配足够的栈空间,避免计算过程中的重复内存分配numStack.reserve(numbers.size()); // 操作数栈预留空间opStack.reserve(operators.size()); // 运算符栈预留空间}// 将表达式转换为字符串形式,用于显示和输出// 格式:操作数1 运算符1 操作数2 运算符2 ... 操作数n =std::string toString() const {std::stringstream ss;for (size_t i = 0; i < numbers.size(); i++) {if (i > 0) {// 在运算符前后添加空格,提高可读性ss << " " << getDisplayOperator(operators[i - 1]) << " ";}ss << numbers[i].toString(); // 添加操作数的字符串表示}ss << " = "; // 表达式以等号结束return ss.str();}// 获取表达式的计算结果// 如果结果已缓存则直接返回,否则调用evaluate()计算Fraction getResult() const {return evaluate();}// 检查表达式是否满足所有约束条件// 包括数值范围、非负结果和真分数除法等要求bool isValid(int range) const {// 检查所有操作数是否在指定范围内 [0, range)for (const auto& num : numbers) {if (num < Fraction(0) || num >= Fraction(range)) {return false; // 数值超出范围}}// 检查中间计算结果是否非负// 使用try-catch防止计算过程中出现异常try {Fraction result = evaluate();if (!result.isNonNegative()) return false; // 结果为负数}catch (...) {return false; // 计算过程中出现异常}// 检查所有除法运算的结果是否为真分数for (size_t i = 0; i < operators.size(); i++) {if (operators[i] == "/") {// 确保有足够的操作数if (i + 1 >= numbers.size()) return false;Fraction left = numbers[i]; // 被除数Fraction right = numbers[i + 1]; // 除数if (right.isZero()) return false; // 除数为零Fraction div_result = left / right;if (!div_result.isProper()) return false; // 除法结果不是真分数}}return true; // 所有检查都通过}// 获取表达式的规范化形式,用于去重比较// 格式:操作数1运算符1操作数2运算符2...操作数n(无空格)std::string getNormalizedForm() const {std::stringstream ss;for (size_t i = 0; i < numbers.size(); i++) {if (i > 0) {ss << operators[i - 1]; // 添加运算符(无空格)}ss << numbers[i].toString(); // 添加操作数}return ss.str();}// 访问器方法:获取操作数列表,主要用于测试const std::vector<Fraction>& getNumbers() const { return numbers; }// 访问器方法:获取运算符列表,主要用于测试const std::vector<std::string>& getOperators() const { return operators; }// 清除缓存:当表达式被修改时需要调用此方法void clearCache() {isResultCached = false; // 重置缓存标记}
};
ProblemGenerator
点击查看代码
class ProblemGenerator {
private:int range; // 数值范围,控制生成的数字大小std::mt19937 gen; // 随机数生成器,用于产生各种随机值// 生成随机数,支持生成整数和分数两种类型// 根据概率控制决定生成整数还是分数,确保数值在指定范围内Fraction generateNumber() {std::uniform_int_distribution<> dis(1, std::max(1, range - 1));// 概率控制:70%概率生成整数,30%概率生成真分数// 只有在范围大于等于2时才可能生成分数if (range >= 2 && std::uniform_real_distribution<>(0, 1)(gen) < 0.3) {// 生成真分数:分母在[2, range]范围内,分子在[1, 分母-1]范围内int den = std::uniform_int_distribution<>(2, range)(gen); // 随机分母int num = std::uniform_int_distribution<>(1, den - 1)(gen); // 随机分子,确保是真分数return Fraction(num, den);}else {// 生成整数:数值在[0, range-1]范围内return Fraction(std::uniform_int_distribution<>(0, range - 1)(gen), 1);}}// 生成随机运算符// 从四种基本运算符中随机选择一种std::string generateOperator() {static std::vector<std::string> ops = { "+", "-", "*", "/" }; // 可用的运算符集合std::uniform_int_distribution<> dis(0, ops.size() - 1); // 随机索引生成器return ops[dis(gen)]; // 返回随机选择的运算符}public:// 构造函数:初始化数值范围和随机数生成器// 使用真随机数种子确保每次运行生成不同的题目ProblemGenerator(int r) : range(r) {std::random_device rd; // 真随机数设备,用于生成种子gen.seed(rd()); // 用真随机数初始化伪随机数生成器}// 生成有效的数学表达式// 通过多次尝试确保生成的表达式满足所有约束条件Expression generate() {int attempts = 0; // 尝试计数器const int maxAttempts = 1000; // 最大尝试次数,防止无限循环// 尝试生成有效表达式,直到成功或达到最大尝试次数while (attempts < maxAttempts) {// 随机决定运算符数量(1-3个),对应2-4个操作数int opCount = std::uniform_int_distribution<>(1, 3)(gen);std::vector<Fraction> numbers; // 操作数容器std::vector<std::string> operators; // 运算符容器// 构建表达式结构:生成操作数和运算符for (int i = 0; i <= opCount; i++) {numbers.push_back(generateNumber()); // 生成操作数if (i < opCount) {operators.push_back(generateOperator()); // 生成运算符}}// 创建表达式对象并验证其有效性Expression expr(numbers, operators);if (expr.isValid(range)) {return expr; // 返回有效的表达式}attempts++; // 增加尝试计数}// 保底机制:如果多次尝试都失败,返回一个简单的有效表达式// 确保函数始终有返回值,避免生成失败return Expression({ Fraction(1), Fraction(1) }, { "+" });}
};
Application
点击查看代码
class Application {
private:// 生成指定数量的四则运算题目并保存到文件// 同时生成对应的答案文件,确保题目不重复且符合所有约束条件void generateProblems(int count, int range) {ProblemGenerator generator(range); // 创建题目生成器,指定数值范围std::set<std::string> generatedExpressions; // 用于存储已生成表达式的规范化形式,实现去重std::vector<Expression> problems; // 存储有效的题目表达式std::vector<Fraction> answers; // 存储对应的答案// 打开输出文件,用于保存题目和答案std::ofstream exerciseFile("Exercises.txt");std::ofstream answerFile("Answers.txt");// 检查文件是否成功打开if (!exerciseFile.is_open() || !answerFile.is_open()) {std::cerr << "无法打开输出文件!" << std::endl;return;}int generated = 0; // 已生成的题目数量const int maxAttempts = count * 10; // 最大尝试次数,防止无限循环int attempts = 0; // 当前尝试次数// 循环生成题目,直到达到指定数量或超过最大尝试次数while (generated < count && attempts < maxAttempts) {Expression problem = generator.generate(); // 生成一个题目表达式std::string normalized = problem.getNormalizedForm(); // 获取规范化形式用于去重// 检查表达式是否重复,基于规范化形式进行比较if (generatedExpressions.find(normalized) == generatedExpressions.end()) {generatedExpressions.insert(normalized); // 添加到已生成集合problems.push_back(problem); // 添加到题目列表answers.push_back(problem.getResult()); // 计算并存储答案generated++; // 增加已生成计数}attempts++; // 增加尝试计数}// 将题目和答案写入文件for (size_t i = 0; i < problems.size(); i++) {// 写入题目文件:序号. 表达式 = exerciseFile << (i + 1) << ". " << problems[i].toString() << std::endl;// 写入答案文件:序号. 答案answerFile << (i + 1) << ". " << answers[i].toString() << std::endl;}// 关闭文件流exerciseFile.close();answerFile.close();// 输出生成结果信息std::cout << "已生成 " << problems.size() << " 道题目到 Exercises.txt 和 Answers.txt" << std::endl;}// 检查答案文件的正确性,对比题目文件和答案文件// 生成统计报告,显示正确和错误的题目编号void checkAnswers(const std::string& exerciseFile, const std::string& answerFile) {// 打开题目文件和答案文件std::ifstream exFile(exerciseFile);std::ifstream ansFile(answerFile);// 检查文件是否成功打开if (!exFile.is_open() || !ansFile.is_open()) {std::cerr << "无法打开输入文件!" << std::endl;return;}std::vector<int> correct, wrong; // 存储正确和错误题目的编号std::string exLine, ansLine; // 用于读取文件的每一行int lineNum = 1; // 当前处理的题目编号// 逐行读取题目文件和答案文件while (std::getline(exFile, exLine) && std::getline(ansFile, ansLine)) {// 提取表达式和答案字符串,跳过序号部分size_t exPos = exLine.find(". ");size_t ansPos = ansLine.find(". ");// 确保找到了序号分隔符if (exPos != std::string::npos && ansPos != std::string::npos) {std::string expressionStr = exLine.substr(exPos + 2); // 提取表达式部分std::string givenAnswer = ansLine.substr(ansPos + 2); // 提取答案部分// 移除表达式末尾的 "= ",还原为纯表达式if (expressionStr.find(" = ") != std::string::npos) {expressionStr = expressionStr.substr(0, expressionStr.length() - 3);}try {// 解析表达式字符串为Expression对象Expression expr = parseExpression(expressionStr);// 计算表达式的正确结果Fraction computedAnswer = expr.getResult();// 将答案文件中的字符串转换为Fraction对象Fraction givenAnswerFrac(givenAnswer);// 比较计算结果与给定答案if (computedAnswer == givenAnswerFrac) {correct.push_back(lineNum); // 答案正确,记录题目编号}else {wrong.push_back(lineNum); // 答案错误,记录题目编号}}catch (...) {// 解析或计算过程中出现异常,视为错误答案wrong.push_back(lineNum);}}lineNum++; // 处理下一题}// 关闭文件流exFile.close();ansFile.close();// 生成批改结果文件std::ofstream gradeFile("Grade.txt");// 写入正确答案统计gradeFile << "Correct: " << correct.size() << " (";for (size_t i = 0; i < correct.size(); i++) {gradeFile << correct[i]; // 写入题目编号if (i < correct.size() - 1) gradeFile << ", "; // 添加逗号分隔}gradeFile << ")" << std::endl;// 写入错误答案统计gradeFile << "Wrong: " << wrong.size() << " (";for (size_t i = 0; i < wrong.size(); i++) {gradeFile << wrong[i]; // 写入题目编号if (i < wrong.size() - 1) gradeFile << ", "; // 添加逗号分隔}gradeFile << ")" << std::endl;gradeFile.close(); // 关闭批改结果文件std::cout << "统计结果已输出到 Grade.txt" << std::endl;}// 解析表达式字符串,将其转换为Expression对象// 支持从文件读取的表达式格式转换为内部表示Expression parseExpression(const std::string& expr) {std::vector<Fraction> numbers; // 存储解析出的操作数std::vector<std::string> operators; // 存储解析出的运算符std::stringstream ss(expr); // 使用字符串流进行分词std::string token; // 存储每个分词// 逐个处理分词while (ss >> token) {if (token == "+" || token == "-" || token == "×" || token == "÷") {// 处理运算符:将显示格式转换为内部格式if (token == "×") operators.push_back("*"); // 乘法符号转换else if (token == "÷") operators.push_back("/"); // 除法符号转换else operators.push_back(token); // 加减法符号保持不变}else {// 处理数字:使用Fraction的字符串构造函数解析各种格式numbers.push_back(Fraction(token));}}// 使用解析出的操作数和运算符创建Expression对象return Expression(numbers, operators);}// 显示程序使用帮助信息// 说明命令行参数的使用方法和示例void showHelp() {std::cout << "使用方法:" << std::endl;std::cout << "生成题目: Myapp.exe -n <题目数量> -r <数值范围>" << std::endl;std::cout << "检查答案: Myapp.exe -e <题目文件> -a <答案文件>" << std::endl;std::cout << "示例:" << std::endl;std::cout << " Myapp.exe -n 10 -r 10" << std::endl;std::cout << " Myapp.exe -e Exercises.txt -a Answers.txt" << std::endl;}public:// 应用程序主入口函数// 解析命令行参数并执行相应的功能void run(int argc, char* argv[]) {// 如果没有提供参数,显示帮助信息if (argc < 2) {showHelp();return;}// 解析命令行参数,存储为键值对std::map<std::string, std::string> params;for (int i = 1; i < argc; i += 2) {if (i + 1 < argc) {params[argv[i]] = argv[i + 1]; // 参数名作为键,下一个参数作为值}}// 检查是否为生成题目模式if (params.find("-n") != params.end() && params.find("-r") != params.end()) {// 提取题目数量和数值范围参数int count = std::stoi(params["-n"]);int range = std::stoi(params["-r"]);// 参数验证:必须为正整数if (count <= 0 || range <= 0) {std::cerr << "参数必须为正整数!" << std::endl;return;}// 参数验证:题目数量不能超过限制if (count > 10000) {std::cerr << "题目数量不能超过10000!" << std::endl;return;}// 执行题目生成功能generateProblems(count, range);}// 检查是否为答案检查模式else if (params.find("-e") != params.end() && params.find("-a") != params.end()) {// 执行答案检查功能checkAnswers(params["-e"], params["-a"]);}// 参数不匹配,显示错误信息和帮助else {std::cerr << "参数错误!" << std::endl;showHelp();}}
};
6.测试运行
测试结果
测试部分例子
exercise.txt
answer.txt
grade.txt
- 计算正确性验证
- 运算符优先级:确保乘除法优先于加减法
- 结果验证:每个测试用例都通过手动计算验证
- 约束条件
- 非负结果:所有表达式验证确保不产生负数
- 真分数除法:除法运算结果是真分数
- 数值范围:所有数值都在指定范围内
运算符数量:不超过3个运算符
- 边界情况处理
- 零的处理:正确处理零的运算和显示
- 负分数:负分数的运算和显示无错误
- 除零保护:防止除零出错
7.项目小结
感想
1.
这次合作开发程序,是一次非常宝贵和高效的体验。
在合作初期,我们首先对任务进行了拆分,并明确了各自负责的模块。过程中,我们不可避免地遇到了一些技术思路和实现方式上的分歧。但通过定期的代码审查和集中讨论,我们不仅高效地解决了问题,还相互借鉴了更好的编程习惯和算法思路。我的搭档莫圣韬在架构设计上很有远见,而我在细节处理和调试方面比较擅长,这种优势互补让我们的代码质量远超个人独立完成的水准。
更重要的是,这次合作让我深切体会到团队协作的力量。当遇到棘手的技术难题时,能够随时与伙伴探讨,极大地减轻了心理压力,也加速了问题的解决。最终程序的成功,是我们共同智慧和努力的结晶。
总而言之,这是一次非常成功的合作。它不仅圆满完成了开发任务,更让我在沟通协作和技术层面都获得了新的成长。
2.
在这次结对开发小学四则运算程序的过程中,我与队友深刻体会到了协作编程的魅力。我们分工明确,通过频繁的代码审查和设计讨论,确保了各模块的无缝衔接。。双栈算法的实现让我们领略了数据结构的美妙,而分数运算的完整性则考验着我们对细节的把握。面对复杂的业务约束,我们共同探讨解决方案,在争论中达成共识,在合作中相互学习。这个项目不仅提升了我们的编程能力,更培养了团队协作精神。当看到程序成功生成万道题目并准确批改时,所有的调试艰辛都化为了成就的喜悦。这次经历让我们明白,优秀的软件不仅是技术的结晶,更是团队智慧与默契的产物。
建议
代码规范与风格统一:
- 使用一致的命名规范(如变量、函数、类名的命名)。
- 统一代码格式(如缩进、括号风格等)。
- 可以使用代码格式化工具(如ClangFormat)来自动格式化代码。
模块化设计:
- 将系统划分为清晰的模块,每个模块由一个人负责。
- 定义好模块之间的接口,便于并行开发。
版本控制:
- 使用Git等版本控制工具,并制定分支管理策略(如Git Flow)。
- 定期提交代码,并编写清晰的提交信息。
代码审查:
- 互相审查代码,确保代码质量,并分享知识。