一个算数表达式(以下简称为表达式)由运算数、运算符、左括号和右括号组成,定义一个枚举类型TokenType表示为:
1 typedef enum { 2 BEGIN, 3 NUMBER, 4 OPERATOR, 5 LEFT_BRAC, 6 RIGHT_BRAC 7 } TokenType;
BEGIN用来表示表达式的开始,稍后会再提及到它。
对表达式进行求值需要借助数据结构栈,C++的标准模板库中包含stack类型,只需要包含头文件stack并引用命名空间std就可以使用了。整个求值过程总共需要2个栈,分别用来存储运算数和运算符;基本求值过程是这样的:比较当前运算符和运算符栈中栈顶运算符的优先级,若栈顶运算符优先级高于当前运算符,则从运算数栈中弹出两个运算数,使用运算符栈栈顶运算符进行计算后再压入运算数栈,并将当前运算符压入运算符栈,否则只将当前运算符压入运算符栈;最后反复上述运算压栈过程直至运算符栈为空,运算数栈的栈顶元素即为运算结果。Calculator类声明如下:
1 class Calculator 2 { 3 public: 4 double calculate(string expression) throw(string); 5 6 private: 7 stack<double> _stkNumbers; 8 stack<char> _stkOperators; 9 10 static int priority(char op); 11 static double calculate(double d1, char op, double d2) throw(string); 12 13 void calculateStack() throw(string); 14 void dealWithNumber(char *&pToken) throw(string); 15 void dealWithOperator(char *&pToken) throw(string); 16 void dealWithLeftBrac(char *&pToken) throw(string); 17 void dealWithRightBrac(char *&pToken) throw(string); 18 };
使用代码描述的运算压栈过程如下:
1 void Calculator::calculateStack() throw (string) { 2 double d2 = _stkNumbers.top(); 3 _stkNumbers.pop(); 4 double d1 = _stkNumbers.top(); 5 _stkNumbers.pop(); 6 char op = _stkOperators.top(); 7 _stkOperators.pop(); 8 _stkNumbers.push(calculate(d1, op, d2)); 9 }
静态成员函数calculate()用于进行简单的四则运算,同时也处理了除数为0的情况:
1 double Calculator::calculate(double d1, char op, double d2) throw (string) { 2 assert(op == '+' || op == '-' || op == '*' || op == '/'); 3 4 cout << d1 << op << d2 << endl; 5 6 if (op == '+') { 7 return d1 + d2; 8 } else if (op == '-') { 9 return d1 - d2; 10 } else if (op == '*') { 11 return d1 * d2; 12 } else { 13 if (!d2) { 14 throw string("divided by 0"); 15 } 16 return d1 / d2; 17 } 18 }
此外,根据数学规则,有:
- 表达式只能以左括号或运算数开始;
- 运算数后只能是右括号或运算符;
- 运算符或左括号后只能是左括号或运算数;
- 右括号后只能是另一个右括号或运算符。
使用代码描述这些数学规则和最后清空运算符栈的过程如下:
1 double Calculator::calculate(string expression) throw (string) { 2 while (!_stkNumbers.empty()) { 3 _stkNumbers.pop(); 4 } 5 while (!_stkOperators.empty()) { 6 _stkOperators.pop(); 7 } 8 TokenType lastToken = BEGIN; 9 10 char * pToken = &expression[0]; 11 while (*pToken) { 12 switch (lastToken) { 13 case BEGIN: 14 if (*pToken == '(') { 15 // an expression begin with a left bracket 16 dealWithLeftBrac(pToken); 17 lastToken = LEFT_BRAC; 18 } else { 19 // or a number 20 dealWithNumber(pToken); 21 lastToken = NUMBER; 22 } 23 break; 24 case NUMBER: 25 // after a number 26 if (*pToken == ')') { 27 // it may be a right bracket 28 dealWithRightBrac(pToken); 29 lastToken = RIGHT_BRAC; 30 } else { 31 // it may be an operator 32 dealWithOperator(pToken); 33 lastToken = OPERATOR; 34 } 35 break; 36 case OPERATOR: 37 case LEFT_BRAC: 38 // after an operator or a left bracket 39 if (*pToken == '(') { 40 // it may be a left bracket 41 dealWithLeftBrac(pToken); 42 lastToken = LEFT_BRAC; 43 } else { 44 // it may be a number 45 dealWithNumber(pToken); 46 lastToken = NUMBER; 47 } 48 break; 49 case RIGHT_BRAC: 50 // after a right bracket 51 if (*pToken == ')') { 52 // it may be another right bracket 53 dealWithRightBrac(pToken); 54 lastToken = RIGHT_BRAC; 55 } else { 56 // it may be an operator 57 dealWithOperator(pToken); 58 lastToken = OPERATOR; 59 } 60 break; 61 } 62 } 63 64 while (!_stkOperators.empty()) { 65 if (_stkOperators.top() == '(') { 66 throw string("bad token '('"); 67 } 68 calculateStack(); 69 } 70 71 assert(!_stkNumbers.empty()); 72 return _stkNumbers.top(); 73 }
lastToken用来指示上一个token的类型,它应该被初始化为BEGIN;在开始求值之前清空运算符栈和运算数栈,可以防止出错,是很有必要的。