结对项目第一步:把实现的四则运算程序的功能划分模块,将不同模块功能分开,从而使模块可复用,并作为独立的部分进行测试。
优化的四则运算程序需要高内聚和低耦合。而我们组写得代码使用了树的数据结构,虽然表达起来简单易懂,算法也比较容易实现,却有着一个很大的缺点:我们在递归生成运算表达式的同事计算了表达式的结果。这样虽然算法和实现的代码都很简便,却不符合模块化的思想。
我们选择的原型程序,一共有输入、随机生成表达式框架(只有运算符但没有数据)、在框架中填入数字并计算结果、输出共四个模块。实现的时候觉得很简单,还以为自己选择了一种不错的算法。不过现在也觉得这种算法不错,起码很新颖,但是基于可复用性强的原则来看,模块的划分就太不合理了,并且不符合低耦合高内聚的要求。根据讨论,我们决定将以前程序的主模块再细分为两个模块,分别为自动生成表达式模块和根据表达式求解模块。总之,就是将以前的模块拆开,然后重组为新的模块。
对于自动生成表达式模块,我们现有的代码还是有很强的实用性的。面临的问题就是:如何根据表达式求解。我们一致认为不应该抛弃原来的数据结构,所以新的模块功能设置为:先获得用户输入的表达式字符串,然后将字符串转换成树结构。
接下来面临的问题是操作数的多样性,首先要将操作数字符串转换为相应的数据类型。
实现如下:
void CharsToInt(char *tmpstr,int flag1, int flag2, int * numer, int * demon,int i) { //根据flag1判断为带分数还是真分数 int j; if (flag1 != 0) { //带分数中的整数部分 char tmpstr1[10]; int tmpint1,tmpint2,tmpint3; for (j = 0; j < flag1; j++) { tmpstr1[j] = tmpstr[j]; } tmpstr1[j] = ‘\0‘; tmpint1 = atoi(tmpstr1); //带分数中的分子部分 for (j = flag1+1 ; j < flag2; j++) { tmpstr1[j - flag1 - 1] = tmpstr[j]; } tmpstr1[j - flag1 -1] = ‘\0‘; tmpint2 = atoi(tmpstr1); //带分数中的分母部分 for (j = flag2+1; j < i; j++) { tmpstr1[j - flag2 -1] = tmpstr[j]; } tmpstr1[j - flag2 -1] = ‘\0‘; tmpint3 = atoi(tmpstr1); *(numer) = tmpint1 * tmpint3 + tmpint2; *(demon) = tmpint2; } else { char tmpstr1[10]; for (j = 0; j < flag2; j++) { tmpstr1[j] = tmpstr[j]; } tmpstr1[j] = ‘\0‘; *(numer) = atoi(tmpstr1); for (j = flag2 + 1; j < i; j++) { tmpstr1[j - flag2 -1] = tmpstr[j]; } tmpstr1[j] = ‘\0‘; *(demon) = atoi(tmpstr1); } }
下一步:由表达式字符串生成树
实现函数中用到了两个栈:操作数栈&运算符栈
从表达式第一位开始读取字符,如果读到的字符不是运算符,那么就是操作数(错误的情况单考虑),操作数有分数和整数。
比如操作数为11‘2/3,这是一个带分数,现在读取到的是‘1‘,根据这个子字符串得到操作数的分子numer,分母demon。在读取的过程中,用两个标志位表示符号 ’ 和符号 /的位置。然后用CharsToInt()将这个子字符串变成Value类型的操作数,即operation.numer = 35,operation.demon = 3.
如果读取的是运算符,还要分运算符是否为括号。为左括号进栈,为右括号则脱括号。
void Experssion::GenerateTree(string expr) { stack <char> operStack;//运算符栈 stack <Experssion *> dataStack;//数据栈 expr += ‘\0‘; char tmpchar,c; int idx = 0; tmpchar = expr[idx++]; Experssion * p; while (operStack.size() != 0 || tmpchar != ‘\0‘) { //如果不是运算符,则接收操作数,这个操作数可能为假分数,真分数,和整数。 if (tmpchar != ‘\0‘ && !IsOper(tmpchar)) { int flag1 = 0;//是否为假分数的标志,为0,不是;不为0,是假分数,且这个数值表示假分数中" ‘ "的位置 int flag2 = 0;//是否为分数的标志,为0,不是;不为0,是分数,且这个数值表示分数中"/"的位置 char tmpstr[10];//暂时保存操作数字符串 int numer,demon;//操作数分子分母 int i = 0; while (tmpchar != ‘\0‘ && !IsOper(tmpchar)) { tmpstr[i] = tmpchar; if (tmpchar == ‘\‘‘)//如果字符为" ‘ " { flag1 = i; } if (tmpchar == ‘/‘) { flag2 = i; } tmpchar = expr[idx++]; i++; } tmpstr[i] = ‘\0‘; //处理这个暂存的字符串,获得分子分母 if (flag1 == 0 && flag2 == 0)//为整数 { demon = 1; numer = atoi(tmpstr); } else//为分数 { CharsToInt(tmpstr,flag1,flag2,&numer,&demon,i); } Value operation(numer,demon); p = new Experssion(); p->Result = operation; dataStack.push(p); } else//读取的是运算符 { switch(tmpchar) { case ‘(‘: operStack.push(‘(‘); tmpchar = expr[idx++]; break; case ‘)‘: while(true) { c = operStack.top(); operStack.pop(); if (c == ‘(‘) { break; } p = new Experssion(); p->oper = c; if (dataStack.size()) { p->right = dataStack.top(); dataStack.pop(); } if (dataStack.size()) { p->left = dataStack.top(); dataStack.pop(); } dataStack.push(p); } tmpchar = expr[idx++]; break; default: if (operStack.size() == 0 || tmpchar != ‘\0‘ && OperLevel(operStack.top()) < OperLevel(tmpchar)) {//进栈 operStack.push(tmpchar); tmpchar = expr[idx++]; } else {//出栈 p = new Experssion(); p->oper = operStack.top(); if (dataStack.size()) { p->right = dataStack.top(); dataStack.pop(); } if (dataStack.size()) { p->left = dataStack.top(); dataStack.pop(); } dataStack.push(p); operStack.pop(); } break; } } } p = dataStack.top(); dataStack.pop(); this->oper = p->oper; this->left = p->left; this->right = p->right; }
最后根据生成的表达式树求解,采用递归的方法:
//由生成的表达式树求解 Value Experssion::GetResult() { if (left != NULL && right != NULL) { Value LResult = left->GetResult(); Value RResult = right->GetResult(); switch(oper) { case ‘+‘: Result = LResult + RResult; break; case ‘-‘: Result = LResult - RResult; break; case ‘*‘: Result = LResult * RResult; break; case ‘>‘: //“>”代替除号 Result = LResult / RResult; break; } return Result; } else { return Result; } }