这个作业属于哪个课程 | 软件工程 |
---|---|
这个作业要求在哪里 | 在这里 |
这个作业的目标 | 写个小学四则运算题目生成器,学会与同伴合作做项目 |
陈威衡 3121005206
郑贵南 3121005237
需求分析
- 表达式中的分数都用真分数的形式表示
- 生成的表达式里只包含四则运算和括号
- 每个表达式最多有2个运算符
- 每个表达式的值都需要计算出来
- 能够通过命令行传参的方式控制输出表达式范围和数量
设计实现过程
- 对于第一点,为了把所有类型的数字统一,我们设计了一个类来表示
class Number
{
public:
long long inte; // 整数部分
long long nume; // 分子
long long deno; // 分母
Number();
Number(long long range);
void show(); //在命令行中显示
bool normalize(); //化为真分数,判断是否为负
bool iszero(); //判零
void writeFile(FILE* f);//写入文件
};
- 当inte为0时表示真分数,当nume为0时表示整数,当二者都不为0时表示带分数
-对于第二和第三点,由于表达式的运算符分别有1,2,3个,所以用三个类来分别表示
//---------一类表达式定义
class Expression1
{
public:
Number num1;
Number num2;
char op; //操作符
Expression1();
Expression1(long long range);
void show();
Number cal(); // 计算值
void writefile(FILE* f);
};
//----二类表达式定义
class Expression2
{
public:
Number num;
Expression1 exp;
char op;
Expression2();
Expression2(long long range);
void show();
Number cal();
void writefile(FILE* f);
};
//三类表达式定义
class Expression3
{
public:
Number num;
Expression2 exp2;
char op;
int type; // 0: num + exp, 1:exp + num
Expression3();
Expression3(long long range);
void show();
Number cal();
void writefile(FILE* f);
};
- 三类表达式的关系为层层嵌套
- 为了方便编写代码,我们重载了Number的四则运算符
Number operator + (const Number& a, const Number& b)
{
Number ans;
ans.inte = a.inte + b.inte;
long long com = lcm(a.deno, b.deno);
ans.deno = com;
ans.nume = com / a.deno * a.nume + com / b.deno * b.nume;
ans.normalize();
return ans;
}
Number operator - (const Number& a, const Number& b) //postive ans
{
Number ans;
ans.inte = a.inte - b.inte;
long long com = lcm(a.deno, b.deno);
ans.deno = com;
ans.nume = com / a.deno * a.nume - com / b.deno * b.nume;
ans.normalize();
return ans;
}
Number operator * (const Number& a, const Number& b)
{
Number ans;
ans.inte = 0;
ans.deno = a.deno * b.deno;
ans.nume = (a.inte * a.deno + a.nume) * (b.inte * b.deno + b.nume);
ans.normalize();
return ans;
}
Number operator / (Number& a, Number& b)
{
Number ans;
ans.inte = -1;
ans.nume = -1;
if (b.iszero() || !b.normalize()) return ans;
ans.inte = 0;
ans.deno = a.deno * (b.inte * b.deno + b.nume);
ans.nume = (a.inte * a.deno + a.nume) * b.deno;
ans.normalize();
return ans;
}
- 四种运算符重载以后,每个表达式的答案便可以计算出来
- 最后,通过随机数来实现生成不同的Number以及表达式运算符
Number::Number(long long range)
{
inte = rand() % range + 1;
deno = rand() % range + 1;
if (deno == 1) deno++;
if (rand() % 2) nume = rand() % deno;
else nume = 0, deno = 1;
normalize();
}
- 对于三类表达式的生成,我们用一个generat函数来执行,其中三类表达式生成的比例为5:3:2
//数量m, 范围r
int generate(long long m, long long r)
{
printf("n = %lld, r = %lld\n", m, r);
printf("generating.....\n");
FILE *fexe, *fans, *fcom;
int err;
err = fopen_s(&fexe, "Exercises.txt", "w+");
if (err)
{
printf("can't open Exercises.txt\n");
return 0;
}
err = fopen_s(&fans, "Answers.txt", "w+");
if (err)
{;
printf("can't open Answers.txt\n");
return 0;
}
err = fopen_s(&fcom, "complete.txt", "w+");
if (err)
{
;
printf("can't open complete.txt\n");
return 0;
}
long long cnt = 0;
Expression1 exp1(r);
Expression2 exp2(r);
Expression3 exp3(r);
Number ans;
while (cnt < m)
{
int rnd = rand() % 1000;
if (rnd < 500)
{
exp1 = Expression1(r);
ans = exp1.cal();
if (ans.iszero() || !ans.normalize())
{
continue;
}
exp1.writefile(fexe);
fprintf(fexe, " = ");
fprintf(fexe, "\n");
ans.writeFile(fans);
fprintf(fans, "\n");
exp1.writefile(fcom);
fprintf(fcom, " = ");
ans.writeFile(fcom);
fprintf(fcom, "\n");
}
else if(rnd < 900)
{
exp2 = Expression2(r);
ans = exp2.cal();
if (ans.iszero() || !ans.normalize()) continue;
exp2.writefile(fexe);
fprintf(fexe, " = ");
fprintf(fexe, "\n");
ans.writeFile(fans);
fprintf(fans, "\n");
exp2.writefile(fcom);
fprintf(fcom, " = ");
ans.writeFile(fcom);
fprintf(fcom, "\n");
}
else
{
exp3 = Expression3(r);
ans = exp3.cal();
if (ans.iszero() || !ans.normalize()) continue;
exp3.writefile(fexe);
fprintf(fexe, " = ");
fprintf(fexe, "\n");
ans.writeFile(fans);
fprintf(fans, "\n");
exp3.writefile(fcom);
fprintf(fcom, " = ");
ans.writeFile(fcom);
fprintf(fcom, "\n");
}
cnt++;
//printf("%lld\n", cnt);
}
fclose(fexe);
fclose(fans);
printf("done!\n");
return 1;
}
- 对于最后一个需求,通过命令行读入参数,再对参数的合法性进行判断
int main(int argc, char* argv[])
{
int seed = time(0);
srand(seed);
long long r = -1;
long long m = 100;
if (argc < 3) printf("too few param");
else if (argc == 3)
{
string param1 = argv[1];
string param2 = argv[2];
long long r = string2ll(param2);
if (param1 != "-r" || r < 100 || r > 1000000) showhelp();
else if (!generate(m, r)) printf("error\n");
}
else if (argc == 4) printf("too few param");
else if (argc == 5)
{
string param1 = argv[1];
string param2 = argv[2];
string param3 = argv[3];
string param4 = argv[4];
if (param1 != "-r" && param3 != "-r") showhelp();
else if (param1 != "-n" && param3 != "-n") showhelp();
else
{
long long num1 = string2ll(param2);
long long num2 = string2ll(param4);
if (num1 < 100 || num2 < 100 || num1 > 1000000 || num2 > 1000000) showhelp();
else
{
if (param1 == "-r") r = num1, m = num2;
else r = num2, m = num1;
if (!generate(m, r)) printf("error\n");
}
}
}
else printf("too much param\n");
return 0;
}
- 若用户输入错误的参数则会有相应的提示
生成结果
- 以范围和数量均为100为例
效能分析
PSP表
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 720 | 20 |
Estimate | 估计这个任务需要多少时间 | 720 | 540 |
Development | 开发 | 120 | 120 |
Analysis | 需求分析(包括学习新技术) | 120 | 30 |
Design Spec | 生成设计文档 | 10 | 10 |
Design Review | 设计复审 | 10 | 10 |
Coding Standard | 代码规范(为目前的开发指定合适的规范) | 10 | 10 |
Design | 具体设计 | 720 | 540 |
Coding | 具体编码 | 120 | 120 |
Code Review | 代码复审 | 60 | 60 |
Test | 测试(自我测试,修改代码,提交修改) | 30 | 30 |
Reporting | 报告 | 60 | 60 |
Test Repor | 测试报告 | 60 | 60 |
Size Measurement | 计算工作量 | 30 | 30 |
Postmortem & Process Improvement Plan | 事后总结,并提出过程改进计划 | 30 | 30 |
合计 | 720 | 540 |
项目小结
- 陈威衡:通过这次结对项目,我收获了很多宝贵的经验。首先,我认识到了团队协作的重要性,一个优秀的团队需要互相支持、及时沟通和不断调整。其次,我也提高了自己的创新思维和实践能力,学会了在实践中不断尝试和优化。此外,多亏同伴对时间的合理安排,我们的项目从原本的周五完成提早到了周四完成。
- 郑贵南:在本次的项目中,通过和同伴的交流,我提高了自己的创新思维和实践能力,学会了如何从需求开始分析一个项目,如何把代码写的简洁易懂,这都是因为同伴在编程和软件设计方面有着深厚的技术基础和丰富的经验。他在解决技术难题时总能提出独特而有效的解决方案,这使我们的项目能够克服许多技术壁垒。