在个人项目中,我们完成了四则运算式的自动生成小程序,在结对项目中我们需要做的是把我们的程序按功能划分模块,将不同的功能模块完全分离,作为独立的部分进行测试。
经过了软件工程理论课的学习,我们知道了判断一个程序设计好坏的标志是类的内聚性是否高,耦合度是否低,能够做到高内聚低耦合的设计才是我们希望看到的,这个时候,再回过头看看自己写的程序,简直想哭。当时写的程序用了树的结构,在递归生成运算表达式的时候顺便完成了表达式结果的求解,这不刚好和要求完全相反吗?这个程序除了获得用户输入,输出之外,其他的生成表达式,计算表达式都写在了一个模块中,完全不符合高内聚的要求。所以我们现在需要做的是将以前程序的主模块再按功能划分为两个模块,分别是自动生成表达式模块和根据表达式求解。
对于自动生成表达式模块,我们现有的代码只需要经过简单的更改就能做到,主要的是如何根据表达式求解。因为我们一开始用的就是树结构,为了重用代码,我们的想法是先获得用户输入的表达式字符串,将此字符串转换成树结构。
在具体完成表达式字符串转换前。我们还需要考虑的是操作数的多样性这个问题,程序得支持用户输入分数和整数,第一步要做的是将表达式字符串中的操作数字符串转换成相应的数据类型。(这个数据类型是个人项目中Value类型数据,私有变量是int numer,int demon,即分子分母)
具体代码:
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); } }
接下来,就是主要函数,由表达式字符串生成树,先上代码:
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; }
这个函数中主要用了两个栈,操作数栈和运算符栈。从表达式第一位开始读取字符,读到的不是运算符,则表明接下来读取的是操作数,操作数支持分数和整数。用一个比较复杂的例子解释:
比如操作数为11‘2/3,这是一个带分数,现在读取到的是‘1‘,根据这个子字符串得到操作数的分子numer,分母demon。在读取的过程中,用两个标志位表示符号 ’ 和符号 /的位置。然后用CharsToInt()将这个子字符串变成Value类型的操作数,即operation.numer = 35,operation.demon = 3.
如果读取的是运算符,还要分运算符是否为括号。为左括号进栈,为右括号则脱括号。
最后根据生成的表达式数求解,代码如下:
//由生成的表达式树求解 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; } }