团队:
盖嘉轩 031602211
许郁杨 031602240
1.0主要是描述一下程序本身,也就是代码和编码规范部分,完整的总结会在完善一些细节后再提交。其实原定是应该在13号提交这篇文章的,只是我写的部分出了点问题。。
其实因为两个人的时间安排问题,导致刚开始没办法常在网上交流,很多问题都是在电话里解决的,难道要把通话记录当证据吗。。总之先用文字阐述一下这个程序的实现过程。
分工
一开始我们就确定了要使用多源文件编写。各个文件的功能分类是我提出方案,然后两个人再一起讨论确定下来的。最初的源文件是这样分类的:
文件名 | 功能 |
---|---|
head | 头文件 |
main | 初始界面 |
calculate | 计算器主体 |
fraction | 生成、计算分数 |
stack | 计算表达式 |
实际上这个分类是有点问题的,很多功能都集中在了个别文件里,只是这个问题写到了stack我们才发现,而且模块分类上也有很多毛病,这也导致了后面千奇百怪的bug。。这个后面会具体说。
最初的分工是这样的:我负责fraction和stack,队友负责其他部分。这也算是最后分工。
1.0的文件分类:
文件名 | 功能 |
---|---|
head | 头文件 |
main | 初始界面 |
generate | 生成表达式 |
fraction | 生成、计算分数 |
stack | 计算表达式 |
verify | 检验答案并输出 |
实现过程中,最初的calculate包括的功能比想象中的多得多。。编写时已经特地分流一些给其他文件了,还是显得有些臃肿,所以最后还是硬拆了一个文件出来。分工没什么变化。
功能模块的分类会在实现思路部分具体地说。
日程规划
不得不感叹一下两个人都很忙啊。。前期基本没时间做,差不多8号之后才开始写代码。
简单的日程规划:
最后。。嗯,晚了一天。。
不过,实际的情况是基本符合规划的。只是13号在测试时发现了一个大坑,填到了今天才勉强填好。
编程规范
编程规范是在模块分类后才一一确定下来的,不过由于变量超乎想象的多,所以变量命名有点混乱。
变量的命名都尽量使用了英文或英文缩写,这里写一下部分变量:
变量名 | 作用 |
---|---|
i、j | 循环变量 |
flag | 标志作用 |
tmp | 临时变量 |
frac | 分数 |
low、high | 数字范围 |
answer | 结果 |
sign | 符号 |
para | 参数 |
infix | 中缀表达式 |
postfix | 后缀表达式 |
point | 栈顶指针 |
函数的命名我们充分发挥了想象力。。
函数名 | 作用 |
---|---|
getRand | 获取随机数 |
getAndCalculate | 获取表达式 |
transEquation | 中缀转为后缀 |
countEquation | 计算后缀的值 |
ifOnly | 判断重复 |
checkAndOutput | 检验答案并输出正确答案 |
finalOut | 输出正误个数 |
gcd | 最大公约数 |
fixUp | 维护分母为正 |
getFrac | 生成分数 |
transFrac | 整数转化为分数 |
simplify | 分数化简 |
transString | 分数转为字符串(不判断整数) |
transToString | 分数转为字符串(判断整数) |
operator +、-、*、/ | 分数四则运算 |
实现思路和遇到的困难
这里主要讲我负责的分数和栈的部分。
最初看到这个题目,觉得最难的部分就是如何处理分数。分数牵扯到了方方面面,比如:
- 如何生成分数;
- 如何保存分数(表达式中分数的格式);
- 分数与整数的关系;
- 如何计算分数;
- 如何在栈中处理分数;
因为曾经看过c++的分数类,所以决定对分数进行单独处理,用一个独立的文件负责分数的生成、格式转换和计算。
为了能方便地分离生成表达式与计算表达式两部分,我们决定将所有数字在计算时当作分数处理(整数/1),将分子与分母分离,对所有数据都进行整型和字符串两种格式的保存。
字符串类型上,因为string能比较方便地进行字符串的组合连接,所以没有使用char[]。但是怎么把数字转换成字符串就有点麻烦了。的确c++中数字和字符串相互转换的方法很多,但是多数还是需要char[]的。我想找到一个除itoa()外的能直接通过string作转换的通用方法,以便处理小规模的数据,毕竟itoa()是平台相关的。最终找到了stringstream,真的超级方便,可以适用于string与各种数据类型的转换。不过这么方便让我不禁怀疑起其中的坑。。不过使用到现在只知道要注意多次转换需要clear(),之后还会继续学习串流的知识。
我通过随机生成两个整数,将大的作为分母,小的作为分子来生成分数。分子分母分别保存后,分数的计算也就简单了。比如加法,答案的分子就是x的分子 × y的分母 + x的分母 × y的分子,分母就是x的分母 × y的分母,最后再化简,就得到了得数。
关于如何在栈中处理分数,我是将所有分数都作加括号处理,而表达式中的括号则用中括号来代替,这样就能将分数作为一个整体来处理。
栈的部分,其实就是将生成的中缀表达式转换成后缀后再计算。不过这次我试用了c++的堆栈容器stack,的确让代码简洁清楚了些。计算后缀时,因为参数和得数都是分数形式的,所以用了分数类数组来作为栈。
表达式计算这部分,除了转换为后缀,也有直接用中缀计算的方法,但是个人觉得后缀的方法更加简洁通用。
除了上面说的,实际实现过程中还碰到了不少问题,特别是在写类的时候,不作不会死。。。其实简单用函数就可以了,只是我对操作符重载挺好奇的,就在这试了一下,结果意外地成功了,虽然还是碰到了很多问题。类的知识我还有好些没能理解,应该说对于类本身我就不是很理解,感觉我就像是在把类当作函数用。。
还有就是计算后缀时,用作将字符转换为数字的变量需要用double,用int计算结果会出错。最初我不知道问题出在哪,所以去翻了网上一些计算中缀表达式的程序,发现了这个差别,只是不知道为什么要用double。。
其他一些零碎的问感觉不是很有写出来的必要,如果有需要补充,会添加在下次的总结中。
代码(个人负责部分)
其他需要注释的部分会在下次添加
/*************************************************************
文件名:fraction.cpp
作者:许郁杨 日期:2016/02/16
描述: 分数类
主要功能包括:分数的生成、转换和四则运算
*************************************************************/
#include"head.h"
int Fraction::greatestCommonDivisor(int x,int y) //最大公约数
{
if (y==0) return x;
else return greatestCommonDivisor(y,x%y);
}
Fraction::Fraction(){ }
Fraction Fraction::getFrac(int l,int h) //生成分数
{
Fraction frac;
int tmp1,tmp2;
char tmpc[MAX];
stringstream tmps5,tmps6;
tmp1=getRand(l,h);
tmp2=getRand(l,h);
frac.numerator=Min(tmp1,tmp2);
frac.denominator=Max(tmp1,tmp2);
tmps5<<frac.numerator;
tmps5>>frac.numerators;
tmps6<<frac.denominator;
tmps6>>frac.denominators;
return frac;
}
Fraction Fraction::transFrac(int up,int down) //整数转换为分数
{
Fraction frac;
stringstream tmps9,tmps10;
frac.numerator=up;
frac.denominator=down;
tmps9<<frac.numerator;
tmps9>>frac.numerators;
tmps10<<frac.denominator;
tmps10>>frac.denominators;
return frac;
}
void Fraction::fixUp(Fraction frac) //维护分母为正
{
if (frac.denominator<0)
{
frac.denominator=-frac.denominator;
frac.numerator=-frac.numerator;
}
}
Fraction Fraction::simplify(Fraction frac) //分数化简
{
int tmp;
char tmpc[MAX];
stringstream tmps7,tmps8;
fixUp(frac);
if (frac.numerator==0) frac.denominator=1;
else
{
tmp=greatestCommonDivisor(fabs(frac.numerator),fabs(frac.denominator));
frac.numerator/=tmp;
frac.denominator/=tmp;
}
tmps7<<frac.numerator;
tmps7>>frac.numerators;
tmps8<<frac.denominator;
tmps8>>frac.denominators;
return frac;
}
string Fraction::transString(Fraction frac) //分数转为字符串(不判断整数)
{
string str;
str="("+frac.numerators+"\"+frac.denominators+")";
return str;
}
string Fraction::transToString(Fraction frac) //分数转为字符串(判断整数)
{
string str;
if (frac.denominator==1) str=frac.numerators;
else str="("+frac.numerators+"\"+frac.denominators+")";
return str;
}
const Fraction operator +(Fraction frac1,Fraction frac2) //加法
{
Fraction answer;
answer.numerator=frac1.numerator*frac2.denominator+frac1.denominator*frac2.numerator;
answer.denominator=frac1.denominator*frac2.denominator;
return answer.simplify(answer);
}
const Fraction operator -(Fraction frac1,Fraction frac2) //减法
{
Fraction answer;
answer.numerator=frac1.numerator*frac2.denominator-frac1.denominator*frac2.numerator;
answer.denominator=frac1.denominator*frac2.denominator;
return answer.simplify(answer);
}
const Fraction operator *(Fraction frac1,Fraction frac2) //乘法
{
Fraction answer;
answer.numerator=frac1.numerator*frac2.numerator;
answer.denominator=frac1.denominator*frac2.denominator;
return answer.simplify(answer);
}
const Fraction operator /(Fraction frac1,Fraction frac2) //除法
{
Fraction answer;
answer.numerator=frac1.numerator*frac2.denominator;
answer.denominator=frac1.denominator*frac2.numerator;
return answer.simplify(answer);
}
/*************************************************************
文件名:stack.cpp
作者:许郁杨 日期:2016/02/16
描述: 栈
主要功能包括:中缀转换为后缀、计算后缀
*************************************************************/
#include"head.h"
stack<char> stored; //运算符栈
void transEquation(string infix,char postfix[]) //中缀转为后缀
{
int i=0,j=0;
while (infix[i]!=' ')
{
if ((infix[i]>='0')&&(infix[i]<='9')) //判断数字
{
while ((infix[i]>='0')&&(infix[i]<='9'))
{
postfix[j]=infix[i];
i++;
j++;
}
postfix[j]='!'; //标识单个整数
j++;
}
if (infix[i]=='(') //判断分数
{
while (infix[i]!=')') //将分数作为整体
{
postfix[j]=infix[i];
i++;
j++;
}
postfix[j]=infix[i];
i++;
j++;
}
if ((infix[i]=='+')||(infix[i]=='-')) //判断'+'、'-'
{
while ((!stored.empty())&&(stored.top()!='['))
{
postfix[j]=stored.top();
j++;
stored.pop();
}
stored.push(infix[i]);
}
if ((infix[i]=='*')||(infix[i]=='/')) //判断'*'、'/'
{
while ((!stored.empty())&&(stored.top()!='[')&&((stored.top()=='*')||(stored.top()=='/')))
{
postfix[j]=stored.top();
j++;
stored.pop();
}
stored.push(infix[i]);
}
if (infix[i]=='[') stored.push(infix[i]); //判断'['
if (infix[i]==']') //判断']'
{
while (stored.top()!='[')
{
postfix[j]=stored.top();
j++;
stored.pop();
}
stored.pop();
}
i++;
}
while (!stored.empty()) //残余运算符
{
postfix[j]=stored.top();
j++;
stored.pop();
}
postfix[j]=' '; //终止符
}
Fraction figure[MAX]; //数栈
string countEquation(string infix) //计算后缀的值
{
int i=0,point=-1;
char postfix[MAX];
transEquation(infix,postfix);
while (postfix[i]!=' ')
{
if ((postfix[i]>='0')&&(postfix[i]<='9')) //整数入栈
{
double k=0; //int会计算出错
while ((postfix[i]>='0')&&(postfix[i]<='9'))
{
k=10*k+postfix[i]-'0';
i++;
}
point++;
figure[point]=figure[point].transFrac(k,1);
}
else
if (postfix[i]=='(') //分数入栈
{
double up=0,down=0; //int会计算出错
i++;
while (postfix[i]!='\')
{
up=10*up+postfix[i]-'0';
i++;
}
i++;
while (postfix[i]!=')')
{
down=10*down+postfix[i]-'0';
i++;
}
point++;
figure[point]=figure[point].transFrac(up,down);
}
else
{
point--;
switch (postfix[i])
{
case '+':figure[point]=figure[point]+figure[point+1];
break;
case '-':figure[point]=figure[point]-figure[point+1];
break;
case '*':figure[point]=figure[point]*figure[point+1];
break;
case '/':figure[point]=figure[point]/figure[point+1];
}
}
i++;
}
return figure[point].transToString(figure[point]);
}
提交记录
因为时间关系,所以提交GitHub很多是由我来做的,实际情况可以看队友博客。如果需要其他的证据截图,会在下次博客补充。