基本思路
一、中綴表達式求值分兩大步:
- 1、将中綴表達式轉換為字尾表達式,統一形式,用空格做間隔;
- 2、字尾表達式求值。
二、中綴表達式轉換為字尾表達式的思路
- 1、處理空格:輸入可能不規範,統一将空格全部删除;此時運算符和括号成為數字的間隔符,友善處理。
- 2、處理單目運算符正負号±;因為它是單目運算符,運算等級要高,使用!來代表-;+号則忽略
- 3、查找運算符及間隔符
- A、運算符不是第一個,則表達式第一個是運算數,運算數直接加入字尾表達式;
- B、運算符是第一個,則要處理運算符:
- B.1、第一個可定是運算符,根據優先等級,确認之前的預備運算符是否出棧,
- B.2、将新的運算符入棧(由于這個有好幾種情況需要分情況處理)
- B.3、如果有左括号,左括号右面~标記一下,查找對應的右括号,遞歸調用運算符處理函數
- C、直到中綴表達式的結尾為止。結尾處用空格來區分。
- D、如果是第一個,而沒有有效的運算符,那麼就到結尾了
- 4、關于()的循環:
- A、遇到(,則從(的下一個字元開始,遞歸調用中綴轉換為字尾的函數。
- B、遇到), 則跳出目前的遞歸層。
三、字尾表達式的求值
-
1、将字尾表達式的内容,根據類型壓棧和出棧:
如果為數值則壓棧;
如果為運算符,則将運算符對應的運算數出棧;計算結果壓棧。
- 2、重複此過程,直到表達式讀取結束,此時棧内有一個數,即為結果。
四、定義一個全局unorder_map,用來記錄不同運算符的優先級。
程式主體:
#include<stack>
#include<string>
#include<iostream>
#include<sstream>
#include<vector>
#include<functional>
#include<unordered_map>
#include<cmath>
using std::unordered_map;
using std::function;
using std::string;
using std::stack;
using std::cin;
using std::cout;
using std::endl;
using std::vector;
//使用類型别名,友善處理不同的類型
typedef double ValueType;
//定義基本的函數操作 加、減、乘、除及指數運算;此處使用了lambd表達式和函數
auto add = [](const ValueType &lhs, const ValueType &rhs)->ValueType { return lhs + rhs; };
auto minus = [](const ValueType &lhs, const ValueType &rhs)->ValueType { return lhs - rhs; };
auto multi = [](const ValueType &lhs, const ValueType &rhs) { return lhs * rhs; };
ValueType divide(const ValueType &lhs, const ValueType &rhs) {
if (rhs == 0)
{
throw std::exception("Divisor is zero.");
return 0;
}
else
return (rhs / lhs);
}
//定義函數調用的字典,使用了可調用對象
unordered_map<char, function<ValueType(const ValueType&,const ValueType&)>> Binops = {
{'+',add},{'-',minus},{'*', multi},{'/',divide},{'^',std::powl}
};
//定義函數優先級的字典,其中‘!’為單目運算符負号
//最好聲明成const,但const不支援[],因為map和set的[]操作會插入沒有的值,也就是改變map和set
unordered_map<char,int> Priority = {
{'+',1},{'-',1},{'*', 2},{'/',2},{'^',3},{'!',4}
};
//下面四個函數是中綴轉換為字尾用到的函數。
//規範中綴表達式
string deal_black(const string &line);
//中綴表達式轉換成字尾表達式的函數
int change_expression(string &midExpr, string &postExpr);
//處理轉換過程中運算符的函數
void deal_op(const string &opAll, size_t posk, stack<char> &opTmp, string &postExpr);
//轉換過程中,處理表達式開頭的函數;開頭單獨處理的原因是開頭可能直接是正負号,表達式中正負号不可能單獨存在。
void deal_begin(string &line, stack<char> &opTmp);
//字尾表達式求值的函數
int get_value(string & expression);
//求值過程中調用的處理運算的函數。
int evaluation(const char&c, stack<double> *stackPtr);
//處理運算符,此處沒有括号,括号的情況已經在前處理了。
//此處運算符最多兩個;兩個的話,第二個是正負号。
void deal_op(const string &opAll, size_t posk, stack<char> &opTmp, string &postExpr)
{
while (!opTmp.empty())
{
if (Priority[opTmp.top()] >= Priority[opAll[0]])
{
postExpr.push_back(' ');
postExpr.push_back(opTmp.top());
opTmp.pop();
}
else
{
break;
}
}
opTmp.push(opAll[0]);
if (posk == 2 && opAll[1] == '-') //如果存在負号的話
opTmp.push('!');
}
//進行中序表達式中的空格
string deal_black(const string &line)
{
string retVal;
for (auto ch : line)
{
if (ch != ' ')
retVal.push_back(ch);
}
return retVal;
}
//處理每個表達式的開始,開始可能有左括号,表示多重括号;可能 有正負号;不考慮其他情況,如表達式中()裡面沒表達式的情況。
void deal_begin(string &line, stack<char> &opTmp)
{
string str{ "+-( " }; //表達式開頭隻可能有這幾種運算符
auto posOp = line.find_first_of(str);
//沒有這些符号就不用考慮了,直接處理。
if (posOp != 0)
return;
auto posNum = line.find_first_not_of(str);//一個新的表達式,一定有數字
auto opAll = line.substr(posOp, posNum - posOp);
//查找左括号
auto posk = opAll.find('(');
//如果有(,隻處理(前的符号,跳到(開始處理
if (posk != string::npos)
{
if (opAll.find('-') != string::npos && opAll.find('-') < posk)
opTmp.push('!');
line = line.substr(posk);
}
else//沒有有(,負号,跳過開始的符号開始處理。
{
if (opAll.find('-') != string::npos)
opTmp.push('!');
line = line.substr(posNum);
}
}
int change_expression(string &midExpr, string &postExpr)
{
/* 中綴表達式求值時,目前運算符是否求值,需要跟它後面的運算符比較:如果大于等于後面的運算符的優先級
則可以計算,即可以加入字尾表達式;否則先記錄在一個運算符的棧中,由前面的入棧條件可知,目前棧頂的運算
符優先級最高 */
stack<char> opTmp; //用于暫存優先級較低的運算符
string str{ "+-*/()^ " }; //需要跳過的運算符和結尾空格
//處理表達式開始的符号,可能為左括号或者正負号
deal_begin(midExpr, opTmp);
string opValid;//用于記錄目前的運算符;
while (!midExpr.empty())
{
//由于,我們的尋找符号包含空格,而表達式以空格結尾,是以此處查找比成功
auto posOp = midExpr.find_first_of(str);
//此時表達式前端為運算符的情況
if (posOp == 0)
{
//找到第一個非運算符,即數字;數字之前的都是運算符。
auto posNum = midExpr.find_first_not_of(str);
//已經到達表達式的結尾了或是括号遞歸的結束;括号疊代結束則退出遞歸;空格代表表達式結束
if (posNum == string::npos && midExpr[0] == ' ')
break;
//如果為右括号,表達式掃描前進一個字元,然後退出遞歸(右括号前面不可能有運算符)
if (midExpr[0] == ')')
{
midExpr = midExpr.substr(1);
break;
}
//提取本次掃描所有的運算符
auto opAll = midExpr.substr(posOp, posNum - posOp);
//找到左括号的位置
auto posk = opAll.find('(');
switch (posk)
{
//沒有括号,正常處理運算符
case string::npos: deal_op(opAll, posNum, opTmp, postExpr);
midExpr = midExpr.substr(posNum); break;
//第一個就是括号,直接遞歸處理
case 0: midExpr = midExpr.substr(posk + 1); change_expression(midExpr, postExpr); break;
//其他情況,先處理括号前的運算符,再遞歸處理
default: deal_op(opAll, posk, opTmp, postExpr);
midExpr = midExpr.substr(posk + 1); change_expression(midExpr, postExpr); break;
}
}
//如果表達式處理到數字,加入到字尾表達式中。
else
{
//字尾表達式以空格為分隔
if(!postExpr.empty())
postExpr.push_back(' ');
postExpr.append(midExpr, 0, posOp);
//下次處理從運算符開始。
midExpr = midExpr.substr(posOp);
}
}
//如果表達式處理完了,把暫存棧中的運算符出棧,加入到字尾表達式
while (!opTmp.empty())
{
postExpr.push_back(' ');
postExpr.push_back(opTmp.top());
opTmp.pop();
}
return 0;
}
int get_value(string & expression)
{
string str;
stack<double> ret_value;
double number;
std::istringstream strin(expression);
while (strin >> str)
{
if (str.size() == 1 && (str[0] <'0' || str[0] >'9'))
{
char c = str[0];
evaluation(c, &ret_value);
}
else
{
number = std::stod(str,nullptr);
ret_value.push(number);
}
}
cout << "The value of expression \"" << expression << "\" is " << ret_value.top() << endl;
return 0;
}
int evaluation(const char&c, stack<double> *stackPtr)
{
if (stackPtr->empty())
return 1;
auto second = stackPtr->top();
stackPtr->pop();
if (c == '!')
{
second = -second;
stackPtr->push(second);
}
else
{
if (stackPtr->empty())
return 1;
auto first = stackPtr->top();
stackPtr->pop();
stackPtr->push(Binops[c](first, second));
}
return 0;
}
//入口主函數
void main()
{
//輸入表達式,輸入為一行
string line;
getline(cin, line);
//分别記錄中綴和字尾表達式
string midExpr, postExpr;
bool isNegative = false;
//處理表達式,使其轉換為标準的中綴表達式“删除表達式内的空格,表達式以空格結尾”
midExpr = deal_black(line);
midExpr.push_back(' ');
//将中綴表達式轉換為字尾表達式
change_expression(midExpr, postExpr);
//字尾表達式求值
get_value(postExpr);
return 0;
}