栈的应用有很多,我们最熟悉的函数调用与递归等对编译器来说都是用栈的工作原理来实现的。还有我们的浏览器中有一个向后退选项,这就是一个栈的应用了(这个是<大话数据结构>中的举例)。还有就是本篇要讲的表达式求值了。
我们知道C/C++是基于表达式的程序设计语言,可见表达式对于语言的重要性了。至于什么是表达式等话题这里就不讨论了,直接说表达式的三种类型,他们分别是:前缀表达式,中缀表达式,后缀表达式。在这里只讲对于双目运算符的情况,单目与三目等不作讨论。
中缀表示形式:<操作数><操作符><操作数>,例如A+B
前缀表示形式:<操作符><操作数><操作数>,例如+AB
后缀表示形式:<操作数><操作数><操作数>,例如AB+
利用中缀形式求表达式的值,在各种程序设计语言和计算器中也经常使用,需要使用两个栈,一个存放操作数,一个存放操作符。在这里我们只讲述如何利用后缀形式求表达式的值。
利用后缀形式求表达式的值只需要一个栈即可,且操作方便,但由中缀形式的表达式转换为后缀形式就需要一个算法与过程了。
先给出一个中缀形式的表达式:A + B * (C - D) - E / F,其后缀形式为:ABCD- * + EF / -。如何将其转换为一个后缀形式的表达式呢?这需要一个算法实现,在展开算法之前要考虑几个问题,一个是怎么将操作符的优先级表现在其后缀形式的顺序中,一个是括号怎么处理。
针对第一个问题,我们将各个常见的二目运算符进行一个优先级的排序
操作符 ch # ( *,/,% +,- )
isp 0 1 6 3 6
icp 0 6 4 2 1
因为要先压栈一个符号作为标志,所以定义#为0,意思是所有的操作符都比他的优先级高。isp与icp是操作符在栈内栈外的优先级是不同的,isp(in stack prioroty) ,icp(in coming priority)。
现在问题都解决了,就是一个算法的实现了,下面请看算法:
<1> 操作符栈初始化,将结束符'#'压入栈。然后读入中缀表达式的首字符ch。
<2> 重复执行以下步骤,直到ch = '#'同时栈顶的操作符也是'#'时,停止循环。
a:若ch是操作符直接输出,读入下一个字符ch。
b:若是操作符,判断ch的优先级icp值域位于栈顶的操作符op的优先级isp值:
@若icp(ch) > isp(op),另ch进栈,读入下一个字符。
@若icp(ch) < isp(op),退出栈顶操作符并输出该字符,注意这里没有读入下一个字符而是继续循环比较栈中的元素。这一步专门为了括号考虑的。
@若icp(ch) == isp(op),退栈但不输出,若退出的是'('号,读入下一个字符ch。
<3> 算法结束,输出序列即为所需要的后缀表达式。
下面就贴代码了:
#pragma once
#include<iostream>
//#include<stack>//STL中的栈 自己的栈也可以达到这个效果
#include"S_stack.h"
#include<vector>
#include<string>
using namespace std;
class Calculator
{
private:
string expression_str;//用于用户输入的表达式承接的字符串
vector<char> char_elem;//中缀转换成后缀后的字符容器 动态增长
Stack<float> stk_float;//用于后缀计算表达式的浮点数栈 核心变量
void Do_Operator(char opt_ch);//根据操作符 去栈中去取两个元素计算
bool Get_operands(float &left_optnum,float &right_optnum);//从栈中取出左和右操作数 用于计算
bool is_numelem(char ch);//判断是否为操作数
float exchange_num(char num_ch);//数值型字符转换成数字
void exchange_exp();//中缀转换为后缀的表达式存放在字符数组中,便于后缀计算
int Get_isp(char opt_ch);//返回操作符的栈内优先级数
int Get_icp(char opt_ch);//返回即将进栈操作符的优先级数
void Show_result();//返回计算结果
public:
Calculator(string str);//计算器接受一个字符串型表达式
~Calculator();
void Running();//对外计算接口
};
#include"Calculator.h"
#include<iostream>
using namespace std;
//初始化 一些数据 用于后期计算
Calculator::Calculator(string str)
{
expression_str = str;
}
Calculator::~Calculator()
{
}
//返回操作符的栈内优先级数
int Calculator::Get_isp(char opt_ch)
{
switch(opt_ch)
{
case'#':
return 0; break;
case'(':
return 1; break;
case'*':case'/':case'%':
return 5; break;
case'+':case'-':
return 3; break;
case')':
return 6; break;
}
}
//返回即将进栈操作符的优先级数
int Calculator::Get_icp(char opt_ch)
{
switch(opt_ch)
{
case'#':
return 0; break;
case'(':
return 6; break;
case'*':case'/':case'%':
return 4; break;
case'+':case'-':
return 2; break;
case')':
return 1; break;
}
}
//判断是否为操作数
bool Calculator::is_numelem(char ch)
{
if(48<=ch && ch<=57)//这里的局限性是 只能判断和后期计算10以内的四则运算
return true;
else
return false;
}
//中缀转换为后缀的表达式存放在字符数组中,便于后缀计算
void Calculator::exchange_exp()
{
Stack<char> stk_char;//栈 用于存放操作符 并处理他们的优先级
stk_char.push('#');//先把 标准字符压栈
char isp_ch;//栈内 字符临时变量
string::size_type i=0;
while((stk_char.isEmpty()==false) && (i<expression_str.length()))
{
if(is_numelem(expression_str[i]))//如果是操作数 输出到字符容器中
{
char_elem.push_back(expression_str[i]);
i++;
}
else
{
isp_ch = stk_char.Get_topelem();//弹出栈顶操作符与待进栈的作比较
if(Get_icp(expression_str[i]) > Get_isp(isp_ch))
{
stk_char.push(expression_str[i]);//如果待进栈的优先级高则入栈
i++;
}
else if(Get_icp(expression_str[i]) < Get_isp(isp_ch))
{
char_elem.push_back(stk_char.pop());
}
else
{
if(stk_char.pop() == '(')
i++;
}
}
}
}
//数值型字符转换为数字
float Calculator::exchange_num(char num_ch)
{
switch(num_ch)
{
case'0':
return 0; break;
case'1':
return 1; break;
case'2':
return 2; break;
case'3':
return 3; break;
case'4':
return 4; break;
case'5':
return 5; break;
case'6':
return 6; break;
case'7':
return 7; break;
case'8':
return 8; break;
case'9':
return 9; break;
}
}
//运算函数 并输出结果
void Calculator::Running()
{
exchange_exp();//转换表达式
for(vector<char>::size_type i=0;i<char_elem.size();i++)
{
switch(char_elem[i])
{//当遇到操作符后作出判断
case'+':case'-':case'*':case'/':
{
Do_Operator(char_elem[i]);//根据操作符并取栈头两个元素计算并把并把结果压栈
continue;
}
//当不为操作符时
default:
{
if(is_numelem(char_elem[i]))//判断是否为数字型字符
{
stk_float.push(exchange_num(char_elem[i]));//转换为数字后压栈
continue;
}
else
{
//当运行到此说明有非法字符或是程序未定义的字符 导致运算无法继续进行
cout<<"不合法的字符出现 导致运算出错 建议清栈 重新输入 并检查表达式的合法性"<<endl;
exit(1);
}
}
}//开关语句后括号
}//for结构后括号
Show_result();//显示结果
}
//从栈中取出左和右操作数 用于计算
bool Calculator::Get_operands(float &left_optnum,float &right_optnum)
{
if(stk_float.isEmpty())//判栈空
{
cout<<"缺少右操作数"<<endl;
return false;
}
right_optnum = stk_float.pop();//取右操作数
if(stk_float.isEmpty())//取出一个右操作数后 再次作出判断
{
cout<<"缺少左操作数"<<endl;
return false;
}
left_optnum = stk_float.pop();//取出左操作数
return true;//返回true
}
//根据操作符 去栈中去取两个元素计算
void Calculator::Do_Operator(char opt_ch)
{
float left,right,value;
if(Get_operands(left,right))//栈中取出头两个元素
{
switch(opt_ch)
{
case'+':
{
value = left + right;
stk_float.push(value);//相加后结果压栈
break;
}
case'-':
{
value = left - right;
stk_float.push(value);//相减 压栈
break;
}
case'*':
{
value = left * right;
stk_float.push(value);//相乘 压栈
break;
}
case'/':
if(right == 0)
{
cout<<"/ 操作符 右操作数为0"<<endl;
exit(1);
}
else
{
value = left / right;
stk_float.push(value);//相除 压栈
}
break;
}
}
}
//保留并输出最后结果
void Calculator::Show_result()
{
cout<<"表达式计算结果:"<<endl;
cout<<expression_str<<" = "<<stk_float.Get_topelem();//计算结束后 栈顶元素为计算结果
}