表达式
-
计算规则
从左到右,先乘除,后加减。有括号先算括号 -
构成元素
加(+)、减(-)、乘(*)、除(/)、 括号( () ),也可含空格( ) -
计算过程
先将中缀表达式转换成后缀表达式,再对后缀表达式进行具体求值。
在具体的代码实现中,两个步骤混在一起
需借助两个栈来实现计算。一个操作数栈,用来存储表达式中的操作数;另一个是运算符栈,用来存储表达式中的运算符常见的表达式为中缀表达式,如 9 +(3-1)* 3 + 10 / 2
后缀表达式不含括号,如 9 3 1 - 3 * + 10 2 / + -
优先级设定
优先级针对表达式中的运算符而言,分为栈内优先级、栈外优先级两种。无论栈内栈外,乘除的优先级始终高于加减的优先级,乘除的优先级相同,加减的优先级相同
相同的运算符,栈内的优先级高于栈外的优先级
栈外,‘(’ 的优先级高于所有的运算符;栈内,则低于所有的运算符
(具体代码时,’ ( ’ 直接入栈)
栈外,‘)’ 的优先级低于所有的运算符;栈内,则高于所有的运算符。
(具体代码时,’ ) ’ 不会入栈)
之所以这样分栈内、栈外优先级,个人认为是缘于计算规则中的 “从左到右” 规定。
假设表达式中仅有两个相同优先级的运算符,如 1 + 2 -3。
设左侧的 + 运算符为 lope,右侧的 - 运算符为 rope。按照 从左到右 的计算规则,lope 应比 rope 先参与运算。
在具体代码实现中,需从左至右依次扫描表达式中的每个字符,lope 先入栈。当扫描到 rope 时(此时 rope 尚未入栈,而 lope 已经入栈),为了满足 从左到右 的计算规则(即实现先计算 lope 的目的),需赋予 lope(栈内) 比 rope(栈外) 更高的运算优先级。
简而言之,加减乘除运算符入栈后优先级升高
代码主要阅读顺序:main - fun,在 fun 中按需阅读额外的函数代码
#include <iostream>
#include <string>
#include <stack>
using namespace std;
bool isOpe(char ch) // 判是否为加减乘除运算符
{
switch (ch)
{
case '+':
case '-':
case '*':
case '/':
return true;
default:
return false;
}
}
bool isNum(char ch)
{
if ((ch - '0') >= 0 && (ch - '0') <= 9)
return true;
return false;
}
int cmp(char inStack, char outOfStack)
{
/*
栈内运算符 inStack 栈外运算符 outOfStack
前者优先级大,返回正数 1
后者优先级大,返回负数 -1
运算符栈内元素只可能为 # + — * / (
*/
switch (inStack)
{
case '+':
if (outOfStack == '*' || outOfStack == '/')
{
return -1;
}
else
{
return 1;
}
case '-':
if (outOfStack == '*' || outOfStack == '/')
{
return -1;
}
else
{
return 1;
}
case '*':
return 1;
case '/':
return 1;
case '(':
if (outOfStack == '+' || outOfStack == '-' || outOfStack == '*' || outOfStack == '/')
{
return -1;
}
case '#':
if (outOfStack == '+' || outOfStack == '-' || outOfStack == '*' || outOfStack == '/')
return -1;
}
}
double cal(char ch, double num1, double num2)
{
switch (ch)
{
case '+':
return num1 + num2;
case '-':
return num1 - num2;
case '*':
return num1 * num2;
case '/':
if (num2 == 0)
{
cout << "除数为0" << endl;
exit(-2);
}
else
{
return num1 / num2;
}
}
}
double fun(string str)
{
/*
操作符栈 OpeStack 设为 char 型
操作数栈 NumStack 设为 double 型(考虑到除法运算)
引入 STL 中的 stack 避免编写两种不同的栈相关代码(引入模板也可达到相同目的)
*/
stack<char> OpeStack;
stack<double> NumStack;
/*
初始时,OpeStack 为空,无法进行后续的 操作符优先级比较 运算,
故先入栈一个额外的字符 '#' 来满足后续计算,其优先级比栈内 '(' 低
*/
OpeStack.push('#');
/*
表达式中的操作数位于 运算符和空格之间,
操作数可能仅为 1 位,如 3,也可能多位,如 333
引入 num_flag 来标记操作数的第一位数
进而达到 让 多位的操作数 参与运算 的目的
*/
bool num_flag = false;
// 从左到右,依次扫描表达式中的每个字符
for (int i = 0; i < str.length(); i++)
{
if (str[i] == ' ') // 若字符为空格,则跳过
{
continue;
}
else if (isNum(str[i])) // 若字符为数字,则接下来分析是否为多位的操作数
{
/*
以 12+ 为例
扫描至 1 时,num_flag为 false ,表示其为数字 12 的第一位数字,
直接入栈 NumStack 即可,并修改 num_flag 为 true
扫描至 2 时,num_flag 为true,表示 2 不是 数字 12 的第一位数字
需从 NumStack 中取出 2 前面已入栈的数字,此为数字 1。
故将 NumStack 栈顶 的 1 出栈,与 2 组成数字 12 后
再将完整的多位数数字 12 入栈 NumStack
扫描至 + 时,非数字,num_flag 修改为 false,
表示其前的多位数数字扫描完毕
*/
if (num_flag) // 若为多位的操作数,则将相应字符进行转化为数字
{
/*
STL-stack 中, pop() 的返回类型为 void
为达到使用 出栈元素 的目的,先取栈顶元素,后出栈该元素
*/
double temp = NumStack.top();
NumStack.pop();
temp = temp * 10 + str[i] - '0';
NumStack.push(temp);
num_flag = true;
}
else
{
// 标记操作数的第一位数,直接入栈该数
NumStack.push(str[i] - '0');
num_flag = true;
}
}
else if (isOpe(str[i])) // 若扫描到加减乘除运算符,则与 OpeStack 的栈顶元素进行优先级比较
{
num_flag = false; // 表示其前的多位数数字扫描完毕
if (cmp(OpeStack.top(), str[i]) < 0) // 若栈外运算符的优先级大
{
OpeStack.push(str[i]);
}
else
{
/*
若栈外运算符的优先级小
*/
while (cmp(OpeStack.top(), str[i]) > 0)
{
double num1 = NumStack.top();
NumStack.pop();
double num2 = NumStack.top();
NumStack.pop();
char ch = OpeStack.top();
OpeStack.pop();
/*
表达式越左的操作数,越先入栈 NumStack
实际运算顺序 num2 ch num1
*/
double num3 = cal(ch, num2, num1);
NumStack.push(num3);
}
OpeStack.push(str[i]);
}
}
else if (str[i] == '(') // '(' 直接入栈
{
num_flag = false;
OpeStack.push(str[i]);
}
else if (str[i] == ')') // 扫描至 ')'时,依次出栈 OpeStack 元素参与运算,直至 '('
{
while (OpeStack.top() != '(')
{
double num1 = NumStack.top();
NumStack.pop();
double num2 = NumStack.top();
NumStack.pop();
char ch = OpeStack.top();
OpeStack.pop();
double num3 = cal(ch, num2, num1);
NumStack.push(num3); // 运算结果入栈
}
OpeStack.pop();
}
}
while (OpeStack.top() != '#') // 将 OpeStack 中的剩余运算符出栈参与运算
{
double num1 = NumStack.top();
NumStack.pop();
double num2 = NumStack.top();
NumStack.pop();
char ch = OpeStack.top();
OpeStack.pop();
double num3 = cal(ch, num2, num1);
NumStack.push(num3);
}
return NumStack.top();
}
int main()
{
string str;// 使用 string 字符串来获取表达式
cout << "输入一个表达式" << endl;
cin >> str;
cout << "运算结果为" << endl;
cout << fun(str) << endl;
return 0;
}