四则运算表达式求解
这次写了一个能够实现简单四则运算(+,-,*,/,含括号)的小程序。首先分析一下功能需求与限定吧。
需求与限定
- 输入四则运算表达式,要求用户输入其计算的结果,程序能够判断用户输入是否正确。
- 算式输入的数据为正整数或者正分数,用户输入计算结果为整数或分数(分数以“a/b”的形式表示)。
- 统计用户输入正确的答案个数以及错误的答案个数。
分析
首先不难想到,程序的整体设计思路分为两部分,一部分是中缀表达式转换为后缀表达式,另一部分就是后缀表达式的计算。但在实现的过程中还有一个难点需要注意,就是分数在整个程序运行过程中的存储以及计算。由于输入的限定,分数一定是以“a/b”的形式表示的,所以我们可以将这种表示直接看做两个数相除的形式。由此,解决了输入是分数的问题。计算过程中的分数存储以及分数与整数的计算问题,我们可以通过将整数分数进行规格化的存储来解决,即:我们建立一个表示分数的结构体如下,将整数和分数都以该结构体的形式存储,其中numerator表示分子,denominator表示分母。对于分数a/b的存储方式就是numerator = a,denominator = b,整数c的存储方式就是numerator = c, denominator = 1。通过这样统一规范的存储就使得我们在整个计算的过程变得轻松的多了。
1 typedef long long ll; 2 struct num{ 3 ll numerator, denominator; 4 num(){numerator = 0; denominator = 1;} 5 num(int n) {numerator = n; denominator = 1;} 6 num(int n,int d) {numerator = n; denominator = d;} 7 8 void operator = (num x) 9 { 10 numerator = x.numerator; 11 denominator = x.denominator; 12 } 13 };
实现过程
在实现之初,我们先给出需要的变量的定义,如下:
1 #define maxn 1005 2 3 char nifix[maxn], post[maxn]; 4 char ans[maxn]; 5 int cnt_right, cnt_wrong; 6 bool error; 7 num res, rst;
其中,nifix为输入的算式;
post为转化后的后缀表达式;
ans表示用户输入的结果;
cnt_right, cnt_wrong分别表示用户回答正确的个数以及回答错误的个数;
error标志程序运行过程中是否出错,如:输入的表达式不符合规范,出现除零的情况等;
res表示程序计算出的算式答案;
rst表示用户输入的答案转换为规范化的形式的结果。
中缀表达式转后缀表达式
有了上述一系列的定义,我们就可以开始实现我们的程序了。上面提到程序的实现主要分为两个部分组成,首先我们实现第一部分中缀表达式转换为后缀表达式,中缀表达式转后缀表达式的规则如下:
- 遇到操作数时,直接输出到后缀表达式。
- 遇到左括号时,直接入栈。
- 遇到右括号时,出栈,将栈顶元素直接添加到后缀表达式后,直到遇到左括号(左括号出栈但不添加到后缀表达式中)。
- 遇到操作符时,若栈为空,则直接入栈;若栈不空,则比较栈顶操作符和该操作符优先级,若栈顶操作符优先级(*,/优先级大于+,-优先级)大于等于该操作符,则出栈并输出到后缀表达式中。重复操作直至栈空或不符合出栈规则。然后将该操作符入栈。
- 最后将栈中元素依次出栈输出到后缀表达式中。
上述的规则能够实现将一个中缀表达式转化为后缀表达式。但是,我们不能保证用户输入的合法性,及用户可能输入不符合中缀表达式规则的式子如:12*]-3。所以我们的程序必须实现对错误的判断功能,不能因为错误的输入导致程序崩溃或者计算出错误的答案等等。根据我的总结,输入的中缀表达式的错误可以分为几类:
- 输入字符串中包含非表达式的字符如:a, b, c, [, &, .等等。
- 输入的字符串中括号不匹配,包括缺少左括号如:1+2)*3,缺少右括号如:1*(2+3等。
- 连续两个符号在一块包括++,+-等但不包括‘(’左边为‘+’‘-’‘*’‘/’和‘)’右边为‘+’‘-’‘*’‘/’。
有了以上的分析我们就可以开始具体实现了。首先我们要实现几个功能性函数,如下:
1 bool isNum(char x) //判断是否是数字 2 { 3 return (x >= '0' && x <= '9'); 4 } 5 6 bool isOp(char x) //判断是否是操作符 7 { 8 return (x == '+' || x == '-' || x == '*' || x == '/' || x == '(' || x == ')'); 9 } 10 11 int priority(char x) //返回一个操作符的优先级 12 { 13 if (x == '-' || x == '+') 14 return 1; 15 else if (x == '*' || x == '/') 16 return 2; 17 else if (x == '(') 18 return 0; 19 else 20 return -1; 21 }
然后就是中缀转后缀的主函数了,实现如下:
1 bool nifix_to_post() 2 { 3 memset(post, 0, sizeof(post)); 4 stack<char> s; //操作符栈,用来压操作符 5 /* ************************************************************************************************ 6 # 由于操作数是多位的,所以我们逐位做一个累加暂存的工作,当遇到操作符时,代表此操作符前的数暂存完毕, 7 # 需要将其入栈,但也不是所有操作符前都有操作数,如'*('等,所以我们需要一个标志位来表示某个操作符前 8 # 是否进行过暂存操作数的处理。 9 *///************************************************************************************************** 10 bool havenum = false; 11 int tmp = 0, pos = 0; //tmp为暂存多位数的变量,pos为后缀数组存储位置 12 for (int i = 0; nifix[i] != '