最近老师要求写一个表达式计算器,讲了算法,看上去容易,但是写起来还是有很多地方要注意呀。
-类设计
-unit类--用于存储后缀表达式
unit类我自己感觉设计得不好。设计这个类的主要目的是为了往后缀表达式中存储数据。
后缀表达式中既有数,也有符号,我想不出什么方法可以把这两种数据类型放在一起,于是设计了这个畸形的类……
class unit {
bool is_number;//判断该unit是否是一个数
double true_number=0;//存储数
char true_operator='$';//存储符号。
int priority=5;//存储符号的优先级
int inv=5;//存储符号的目数
public:
unit(const double &true_number_fake);//作为数的构造函数
unit(const char &true_operator_fake);//作为符号的构造函数
~unit() {};
unit& operator=(const unit &A);
char read_operator();//读取符号
double read_number();//读取数
int read_priority();//读取优先级
int read_inv();//读取目数
bool read_is();//判断是否是一个数
int cal_priority();//计算优先级
int cal_inv();//计算目数
void show();//若为一个数,输出true_number,若为一个符号,输出true_operator
};
-caculator类--用于计算
另外设计了一个caculator类用于计算。
class caculator {
string Expression;//中缀表达式
queue<unit> reverse_polish_notation;//后缀表达式
stack<unit> operators;//运算符栈
public:
caculator() {};
~caculator() {};
void input_expression();//输入中缀表达式
double cal_reverse_polish_notation();//计算后置表达式的值
void switch_negative_number();//预处理负数
void switch_into_reverse_polish_notation();//将中置表达式转换为后置表达式
};
-函数设计
负数这个情况十分复杂,没有想到好的转换方法,只能要求在输入表达式的时候将负数统统用括号括起来。后来上网发现这里提供了一种好的解决方案,有时间再实现吧。
switch_negative_number这个函数用于预处理负数。作用是将负号转换为’@’以便同减号区分开来。
void caculator::switch_negative_number() {
for (int i = 0; i < Expression.size()-1; ++i) {
if (Expression[i] == '('&&Expression[i + 1] == '-')
Expression[i + 1] = '@';
}
}
计算过程大概是先预处理负号,再将中缀表达式转换为后缀表达式,最终计算后缀表达式输出结果。
写转换函数费了好大的功夫,转换的方法也是上网查的→_→。转换方法大概是这样的:
一. 从表达式头开始扫描
二. 如果遇到数字,加入后缀表达式中
三. 如果遇到符号
(1). 若栈空,直接入栈
(2). 若栈非空
a. 若为 ‘(‘,入栈。
b. 若为 ‘)’,则依次把栈中的的运算符加入后缀表达式中,直到出现’(‘,从栈中删除’(‘。
c. 若为 除括号外的其他运算符, 当其优先级高于除’(‘以外的栈顶运算符时,直接入栈。否则从栈顶开始,依次弹出比当前处理的运算符优先级高和优先级相等的运算符,直到一个比它优先级低的或者遇到了一个左括号为止。
这个地方表达式用string存储,运算符栈用stack存储,而转换好的后缀表达式用queue存储。
其实一开始我都用了stack类,但是在写计算后缀表达式函数的时候发现要从“左”往“右”计算,也就是说要先处理先压栈的元素,但是stack是先进后出,不可以这样处理。所以这里后缀表达式用queue类存储。
这里需要注意的是数的处理。数在string里面是以“数字”的形式存在的,我们要把一个个数字组合在一起变成一个数。这里加入了小数和负数开关,为了更好地计算数。
要注意的是,数不是一计算完就压栈,而是在遇到了符号之后再压栈,所以表达式一定要加等号,要不然有些时候最后一个数不能压栈。
我还设计了一个检测数是否改变的开关。比如说(3+4)*5这个式子。如果没有这个开关,在检测到左括号后程序就会将0(压栈的数默认为0)压栈。所以一定要设计一个开关用来检测数有没有变过,以免将多余的数压栈。
以下是转换函数:
void caculator::switch_into_reverse_polish_notation() {
bool is_decimal = false;//小数开关
bool is_negative = false;//负数开关
int power = -1;//计算小数用
double temp_number = 0;
bool change_temp_number = false;//检测数字是否改变
for (int x = 0; x < Expression.size(); ++x) {
if (48 <= Expression[x] && Expression[x] <= 57) { //处理数字
if (is_decimal) {
temp_number = temp_number + pow(10, power)*(Expression[x] - 48);
change_temp_number = true;
}
else if (!is_decimal) {
temp_number = temp_number * 10 + Expression[x] - 48;
change_temp_number = true;
}
}
else if (Expression[x] == 46) //处理小数点
is_decimal = true;
else if (Expression[x] == '@')
is_negative = true;
else { //处理运算符
if (change_temp_number) {//如果temp_number有所改变的话,就入栈。针对有时运算符在首位的情况。
if (is_negative)
temp_number = 0 - temp_number;
unit push_number(temp_number);
reverse_polish_notation.push(push_number);
power = -1;
temp_number = 0;
is_decimal = false;
change_temp_number = false;
is_negative = false;
}
if (Expression[x] == 'c' || Expression[x] == 's') {//如果该运算符为三角函数
unit push_operator(Expression[x]);
operators.push(push_operator);
x += 2;
continue;
}
if (Expression[x] == '(') {//若为'(',入栈
unit push_operator(Expression[x]);
operators.push(push_operator);
}
else if (Expression[x] == ')') {//若为')'
while (operators.top().read_operator() != '(') {//直到出现'('
reverse_polish_notation.push(operators.top());//依次把栈中的运算符加入后缀表达式中
operators.pop();
}
operators.pop();//从栈中删除'('
}
else if (Expression[x] == '=')
break;
else {//若为除括号以外的其他运算符
unit temp_op(Expression[x]);
if (operators.empty())//当栈空的时候,入栈
operators.push(temp_op);
else {//当栈不为空的时候
if (temp_op.read_priority() > operators.top().read_priority())//当其优先级高于除'('以外的运算符时
operators.push(temp_op);//直接入栈
else if (temp_op.read_priority() <= operators.top().read_priority()) {//否则
bool is_temp_op_pushed = false;
while (!operators.empty()) {//从栈顶开始
if (!(temp_op.read_priority() > operators.top().read_priority() || operators.top().read_operator() == '(')) {//直到一个比它优先级低的或者遇到了一个左括号为止
reverse_polish_notation.push(operators.top());//依次弹出比当前处理的运算符优先级高和优先级相等的运算符
operators.pop();
}
else {
operators.push(temp_op);
is_temp_op_pushed = true;
break;
}
}
if (operators.empty() && !is_temp_op_pushed)
operators.push(temp_op);
}
}
}
}
}
while (!operators.empty()) {
reverse_polish_notation.push(operators.top());
operators.pop();
}
}
以下是计算函数:
double caculator::cal_reverse_polish_notation() {
stack<unit> result;
for (int i = reverse_polish_notation.size(); i > 0; --i) {
if (reverse_polish_notation.front().read_is()) {
result.push(reverse_polish_notation.front());
reverse_polish_notation.pop();
}
else {
if (reverse_polish_notation.front().read_inv() == 2) {
double a = result.top().read_number();
result.pop();
double b = result.top().read_number();
result.pop();
double c;
switch (reverse_polish_notation.front().read_operator()) {
case'+':c = a + b; break;
case'-':c = b - a; break;
case'*':c = a*b; break;
case'/':c = b / a; break;
}
unit temp(c);
result.push(c);
reverse_polish_notation.pop();
}
else if (reverse_polish_notation.front().read_inv() == 1) {
double a = result.top().read_number();
result.pop();
double c;
switch (reverse_polish_notation.front().read_operator()) {
case'c':c = cos(a); break;
case's':c = sin(a); break;
}
unit temp(c);
result.push(c);
reverse_polish_notation.pop();
}
}
}
return result.top().read_number();
}
要运行的时候依次执行预处理函数、转换函数、计算函数就可以啦。
int main() {
while (1) {
caculator joy;
cout << "请输入表达式,负数请用括号括起来,表达式必须带=" << endl;
joy.input_expression();
joy.switch_negative_number();
joy.switch_into_reverse_polish_notation();
double bb = joy.cal_reverse_polish_notation();
cout << endl << bb << endl;
system("pause");
system("cls");
}
return 0;
}
这是我第一次用markdown写文章,还有很多地方不熟练,希望以后可以有所长进吧:)。