结对项目
协作成员:
林赛强 3123004184
莫桂友 3123004191
psp表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 120 | 150 |
· Estimate | 估计这个任务需要多少时间 | 140 | 150 |
Development | 开发 | 350 | 400 |
· Analysis | 需求分析 (包括学习新技术) | 60 | 60 |
· Design Spec | 生成设计文档 | 20 | 30 |
· Design Review | 设计复审 | 30 | 30 |
· Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
· Design | 具体设计 | 30 | 40 |
· Coding | 具体编码 | 120 | 140 |
· Code Review | 代码复审 | 30 | 50 |
· Test | 测试(自我测试,修改代码,提交修改) | 60 | 60 |
Reporting | 报告 | 80 | 90 |
· Test Repor | 测试报告 | 30 | 30 |
· Size Measurement | 计算工作量 | 20 | 20 |
· Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 1130 | 1290 |
设计实现过程/代码说明
1. 代码组织结构
类名 | 作用 |
---|---|
Fraction |
负责分数的表示、运算和化简 |
主要函数 | 作用 |
---|---|
simplify() |
化简分数 |
operator+ |
分数加法 |
operator- |
分数减法 |
operator* |
分数乘法 |
operator/ |
分数除法 |
infixToPostfix() |
将中缀表达式转换为后缀表达式 |
evaluatePostfix() |
计算后缀表达式 |
generate_Problrm() |
负责随机运算表达式 |
2. 关键函数
1.随机生成模块
功能:生成题目所需的随机操作数和运算符,控制题目难度和类型。
generate_integer(int n)
生成[1, n]
范围内的随机整数。generate_fraction(int n)
生成分子和分母均在[1, n]
范围内的随机分数。generateOperator()
随机返回+
,-
,*
,/
中的一个运算符。
2. 表达式解析模块
功能:将用户输入或生成的中缀表达式转换为计算机易处理的后缀表达式(逆波兰式)。
isOperator(char c)
判断字符是否为四则运算符。getOperatorPrecedence(char op)
定义运算符优先级,*
/
高于+
-
。infixToPostfix(const string& expr)
- 作用:使用 栈结构 将中缀表达式转换为后缀表达式。
- 算法步骤:
- 分割输入表达式为操作数(整数或分数)和运算符。
- 遇到操作数直接加入后缀表达式。
- 遇到运算符时,弹出栈中优先级≥当前运算符的运算符,再压入当前运算符。
- 表达式结束后,将栈中剩余运算符依次弹出。
- 示例:
- 输入:
"3 + 5'1/2 * 2"
- 输出:
["3", "5'1/2", "2", "*", "+"]
- 输入:
点击查看代码
vector<string> infixToPostfix(const string& expression) {
std::stack<char> operators;
std::vector<std::string> postfix;
std::istringstream tokens(expression);
std::string token;
while (tokens >> token) {
if (isdigit(token[0]) || token.find('/') != std::string::npos) {
// 操作数(整数或分数)
postfix.push_back(token);
}
else if (isOperator(token[0])) {
// 运算符
while (!operators.empty()
&& getOperatorPrecedence(operators.top()) >= getOperatorPrecedence(token[0])) {
postfix.push_back(std::string(1, operators.top()));
operators.pop();
}
operators.push(token[0]);
}
else {
cout<< "Invalid character in expression.";
}
}
// 将剩余的运算符添加到后缀表达式
while (!operators.empty()) {
postfix.push_back(std::string(1, operators.top()));
operators.pop();
}
return postfix;
}
3. 表达式计算模块
功能:对后缀表达式进行求值,支持分数运算和带分数处理。
-
evaluatePostfix(const vector<string>& postfix)
**-
作用:通过栈结构计算后缀表达式的值。
-
实现步骤:
- 遍历后缀表达式:
- 操作数:解析为
Fraction
对象并入栈。- 处理带分数(如
3'1/2
):转换为假分数(3*2 + 1)/2 = 7/2
。 - 处理普通分数(如
5/3
):直接构造Fraction(5,3)
。 - 处理整数(如
4
):构造Fraction(4,1)
。
- 处理带分数(如
- 运算符:弹出栈顶两个操作数进行计算,结果压栈。
- 最终栈顶元素为计算结果。
-
-
calculate(const string& expr)
** 整合表达式转换和计算流程。
4. 题目生成模块
功能:生成符合数学规则的算术题目,确保减法不产生负数。
-
generate_Problem(string& problem, int r)
-
核心流程:
- 确定运算符数量:随机生成
1-3
个运算符,控制题目复杂度。 - 生成首操作数:随机选择生成整数或分数。
- 迭代生成后续操作数:
- 加法/乘法:任意生成操作数。
- 减法:生成一个 ≤ 当前结果的操作数(通过
generate_fraction_with_max
实现)。 - 除法:生成非零分母。
实时计算中间结果:用于约束后续操作数的生成范围。
- 确定运算符数量:随机生成
-
点击查看代码
string generate_Problem(string& problem, int r) {
string result; // 存储计算结果
int operator_sum = (rand() % 3) + 1; // 运算符个数(1-3)
bool isFractionProblem = ifgenerateFraction(); // 判断是否为分数题
string operand1_str;
if (isFractionProblem) {
operand1_str = generate_fraction(r).toString(); // 1 生成初始分数
}
else {
operand1_str = to_string(generate_integer(r)); // 0 生成初始整数
}
problem = operand1_str;
result = operand1_str;
for (int i = 0; i < operator_sum; ++i) {
char op = generateOperator(); // 随机生成运算符
Fraction current_value = parseResult(result); // 解析当前结果为Fraction
// 生成第二个操作数
string operand2_str;
if (op == '-') {
if (isFractionProblem) {
// 生成不超过current_value且分母不超过r的分数
Fraction operand2 = generate_fraction_with_max(r, current_value);
operand2_str = operand2.toString();
}
else {
// 生成不超过current_value的整数
int max_op = current_value.integer - 1;
if (max_op <= 0) {
max_op = 1;
}
int operand2_int = generate_integer(max_op);
operand2_str = to_string(operand2_int);
}
}
else {
if (isFractionProblem) {
operand2_str = generate_fraction(r).toString();
}
else {
operand2_str = to_string(generate_integer(r));
}
}
problem += " " + std::string(1, op) + " " + operand2_str;
// 实时计算中间结果 用于约束后续操作数的生成范围
Fraction operand2 = parseResult(operand2_str);
Fraction new_result;
switch (op) {
case '+': new_result = current_value + operand2; break;
case '-': new_result = current_value - operand2; break;
case '*': new_result = current_value * operand2; break;
case '/':
if (operand2.integer == 0 && operand2.numerator == 0) {
cout << "division by zero.";
}
new_result = current_value / operand2;
break;
}
result = new_result.toString();
}
return result;
}
5. 辅助工具模块
功能:提供数据转换和生成支持。
parseResult(const string& str)
- 作用:将字符串解析为
Fraction
对象,支持三种格式:- 整数:
"5"
→Fraction(5, 1)
- 分数:
"3/4"
→Fraction(3,4)
- 带分数:
"2'1/3"
→Fraction(2*3 +1, 3) = Fraction(7,3)
- 整数:
- 作用:将字符串解析为
generate_fraction_with_max(int range, const Fraction& max)
- 作用:生成一个分母 ≤
range
且值 ≤max
的分数。 - 实现:通过循环生成随机分数,直到满足
生成的分数 ≤ max
。
- 作用:生成一个分母 ≤
模块间协作示例
- 生成题目:
generate_Problem
调用generate_integer
或generate_fraction
生成操作数,generateOperator
生成运算符。- 每次生成新操作数后,调用
calculate
计算当前表达式结果,用于后续操作数生成。
- 计算答案:
- 用户输入表达式后,调用
infixToPostfix
转换为后缀表达式,再通过evaluatePostfix
计算结果。
- 用户输入表达式后,调用
- 异常处理:
- 若生成的分母为零,构造函数自动修正;若计算时除数为零,抛出异常并由调试者捕获处理。
效能分析

- 字符串分割效率低,使用 istringstream 按空格分割表达式,每次 >> 操作会产生临时字符串对象,对长表达式(如含多个分数和运算符)会有显著开销。
- Fraction::toString() 分数转字符串函数,频繁使用 stringstream,stringstream 内部动态分配内存,且每次调用会初始化流对象,效率较低。
带分数格式如 3'1/2 需要多次拼接字符串,导致内存操作频繁。
运行结果:
项目小结
结对项目总结
莫桂友:结对项目让我意识到每个人都有独特的优势和不足。通过与伙伴的合作,我们能够互相补充。同时从伙伴身上学到了许多新的技术和解决问题的方法,这让我受益匪浅
林赛强:结对项目让我深刻认识到合作与沟通的重要性。与伙伴的紧密配合是项目成功的关键。通过频繁的交流,我们能够及时发现问题、解决问题,并确保双方对任务的理解一致