前言
之前的版本只能計算單個數字之間的加、減、乘、除、取餘、 乘方。這次首先對輸入的字符串進行正則匹配,分割之後放入棧中。還重新設計了新的優先級,並對中綴表達式和後綴表達式的轉換做了點修改。但是缺點也有,提供的默認函數,如sin\cos\tan\sqrt\log等都是只能有一個參數的函數!!!
一次匹配分割Token
通過正則表達式把數字(其中含小數、指數),函數,符號給分割出來。
需要二次匹配,一次只進行分割,二次把分割得到的Token再分類出來——假如第一次匹配能得到是哪個正則分組匹配到的就不用二次了!
/*
一次正則匹配
\\d+(\\.\\d+)?(e[\\+\\-]?\\d+)? 數字,含小數和指數
[a-zA-Z\\_][a-zA-Z\\d\\_]* 函數,可以有下劃線和數字,開頭不能是數字
[\\+\\-\\*\\/\\^\\%\\(\\)] 符號
*/
regex reg("((\\d+(\\.\\d+)?(e[\\+\\-]?\\d+)?)|([a-zA-Z\\_][a-zA-Z\\d\\_]*)|([\\+\\-\\*\\/\\^\\%\\(\\)]))");
對於形如7.1e2的數字,其中的e2也能算是函數,爲什麼結果被算做了數字而不是函數呢?這我不太清楚,推測是正則匹配時每次只匹配一個分組!!!
二次匹配分類Token
如果字符串裏只有個負數,比如-9,按照前面分割Token的正則的話,這個會分割出運算符負號和數字9。
在計算的時候這麼做沒問題,但是當我想得到最後的結果時,我需要它直接把-9給匹配出來!!!
那麼二次匹配時,數字的匹配就要增加條件,把加減號的匹配。
regex regFunc("[a-zA-Z\\_][a-zA-Z\\d\\_]*");
regex regChar("(\\+|\\-|\\/|\\*|\\^|\\%|\\(|\\))");
regex regNum("[\\+\\-]?\\d+(\\.\\d+)?(e[\\+\\-]?\\d+)?");
在進行條件判斷的時候,要先判斷符號,再判斷數字!這樣-9前面的負號就不能算作負號,而是把-9整體算作數字了。
中綴表達式轉後綴表達式
中綴表達式轉後綴表達式
5+4 => 5 4 +
1+2*3 => 1 2 3 * +
8+(3-1)*5 => 8 3 1 - 5 * +
遍歷中綴表達式中的數字和符號:
1,對於數字,直接轉爲後綴表達式
2,對於符號,
(1)左括號進棧
(2)運算符號與棧頂符號進行優先級比較
1>如果棧頂符號優先級低,此符號進棧
2>如果棧頂符號優先級不低,將棧頂符號彈出並轉爲後綴表達式
3>如果沒有棧頂符號,此此符號進棧
4>數學函數sin,cos,tan,asin,acos,atan,sinh,cosh,tanh,log,sqrt等只有一個參數的數學函數
優先級最高
(3)右括號的話,將棧頂符號彈出並輸出,直到匹配到左括號遍歷結束後將棧中所有符號彈出並轉爲後綴表達式
後綴表達式求解
後綴表達式求解!!!
遍歷後綴表達式中的數字和符號
1,對於數字,直接進棧
2,對於符號
(1)從棧中彈出右操作數
(2)從棧中彈出左操作數
(3)根據符號進行運算
(4)將運算結果壓入棧中
3,對於函數
(1)從棧中彈出棧頂操作數
(2)根據函數進行運算
(3)將運算結果壓入棧中
遍歷結束,棧中唯一數字爲計算結果
優先級
形如sin(3)^2這樣的式子,我設計它先計算正弦函數,再計算乘方,所以優先級最高的就是函數。
- 函數優先級爲1
- ^ 乘方優先級2
- * 乘 / 除 % 取餘優先級3
- + 加 - 減優先級4
- ()括號優先級5
- 其他符號優先級6
- 數字沒有優先級,設爲MyNil
代碼
//calc.h
#pragma once
#include<regex>
#include<iostream>
#include<iomanip>
#include<string>
#include<sstream>
#include<functional>
#include<vector>
#include<cmath>
using namespace std;
///空值,如果使用的數爲這個值就報錯
const int MyNil = 0xffff;
///不同的類型
enum DataType{FuncType,CharType,NumType,OtherType};
class MyData {
private:
string name;///類型名,即輸入的子字符串
DataType type;///類型判斷符
int priority;///優先級
double value;///值,只有類型判斷符爲NumType時纔有
public:
//傳入子字符串,通過該字符串來判斷
MyData(string name);
//拷貝構造函數
MyData(const MyData& mydata);
//重載等於號。每次等於都調用一次默認構造函數太浪費性能了
MyData& operator=(const MyData& mydata);
//返回該符號或者該函數的優先級
int GetPriority()const;
//返回類型名
string GetName()const;
//返回該類型名的值
double GetVal()const;
//判斷是符號,還是數字,還是函數
DataType GetType()const;
};
///後綴表達式與中綴表達式
extern vector<MyData*> chain_suffix ,chain_infix ;
///中綴表達式轉後綴表達式的棧,也是後綴表達式求解的棧
extern vector<MyData*> stack;
/*
中綴表達式轉後綴表達式
5+4 => 5 4 +
1+2*3 => 1 2 3 * +
8+(3-1)*5 => 8 3 1 - 5 * +
遍歷中綴表達式中的數字和符號:
1,對於數字,直接轉爲後綴表達式
2,對於符號,
(1)左括號進棧
(2)運算符號與棧頂符號進行優先級比較
1>如果棧頂符號優先級低,此符號進棧
2>如果棧頂符號優先級不低,將棧頂符號彈出並轉爲後綴表達式
3>如果沒有棧頂符號,此此符號進棧
4>數學函數sin,cos,tan,asin,acos,atan,sinh,cosh,tanh,log,sqrt等只有一個參數的數學函數
優先級最高
(3)右括號的話,將棧頂符號彈出並輸出,直到匹配到左括號
遍歷結束後將棧中所有符號彈出並轉爲後綴表達式
優先級:乘除>加減>括號
後綴表達式求解!!!
遍歷後綴表達式中的數字和符號
1,對於數字,直接進棧
2,對於符號
(1)從棧中彈出右操作數
(2)從棧中彈出左操作數
(3)根據符號進行運算
(4)將運算結果壓入棧中
3,對於函數
(1)從棧中彈出棧頂操作數
(2)根據函數進行運算
(3)將運算結果壓入棧中
遍歷結束,棧中唯一數字爲計算結果
*/
//生成一個MyData指針對象
MyData* CreateMyData(string name);
//數字操作
void NumberOperate(vector<MyData*>::iterator it);
//左括號操作
void LeftParenthesisOperate(vector<MyData*>::iterator it);
//右括號操作
void RightParenthesisOperate();
//符號操作
void OpOperate(vector<MyData*>::iterator it);
//函數操作
void FuncOperate(vector<MyData*>::iterator it);
//通過函數計算後綴表達式的值
double FuncCaculate(double val, string name);
//通過運算符計算後綴表達式的值
double OpCaculate(double left, double right, string name);
//輸入一個字符串,並把它匹配放入中綴表達式中
void InputAstring();
//中綴表達式轉後綴表達式
void Infix2suffix();
//後綴表達式求解
void CaculateSuffix();
//calc.cpp
#include"calc.h"
///後綴表達式與中綴表達式
vector<MyData*> chain_suffix, chain_infix;
///中綴表達式轉後綴表達式的棧,也是後綴表達式求解的棧
vector<MyData*> stack;
//傳入子字符串,通過該字符串來判斷
MyData::MyData(string name) {
this->name = name;
//二次正則匹配
//三角函數,反三角函數,對數函數,四捨五入函數,絕對值函數
//regex regFunc("(sin|cos|tan|sqrt|log|log10|fabs|asin|acos|atan|round)");
//具體的函數交給後面計算的時候判斷
regex regFunc("[a-zA-Z\\_][a-zA-Z\\d\\_]*");
//運算符號
regex regChar("(\\+|\\-|\\/|\\*|\\^|\\%|\\(|\\))");
//加減號,整數,小數,指數
//如果是個負數,比如-9,因爲不能整體匹配運算符號,所以接着匹配數字!!!
//換句話說,運算符號的優先級比數字高
regex regNum("[\\+\\-]?\\d+(\\.\\d+)?(e\\d+)?");
smatch sm;
if (regex_match(name, sm, regFunc)) {
type = FuncType;
priority = 1;
value = MyNil;
}
else if (regex_match(name, sm, regChar)) {
type = CharType;
if(sm.str().compare("^")==0) priority = 2;
else if (sm.str().compare("/") == 0 || sm.str().compare("*") == 0 || sm.str().compare("%") == 0) priority = 3;
else if (sm.str().compare("+") == 0 || sm.str().compare("-") == 0) priority = 4;
else if (sm.str().compare("(") == 0 || sm.str().compare(")") == 0) priority = 5;
else priority = 6;
value = MyNil;
}
else if (regex_match(name, sm, regNum)) {
type = NumType;
priority = MyNil;
value = atof(sm.str().c_str());
}
else {
cout << "[Error in <MyData::MyData>符號 "<<name<<" 未匹配到!]" ;
type = OtherType;
priority = MyNil;
value = MyNil;
}
}
//拷貝構造函數
MyData::MyData(const MyData& mydata) {
this->name = mydata.name;
this->priority = mydata.priority;
this->type = mydata.type;
}
//重載等於號。每次等於都調用一次默認構造函數太浪費性能了
MyData& MyData::operator=(const MyData& mydata) {
this->name = mydata.name;
this->priority = mydata.priority;
this->type = mydata.type;
return *this;
}
//返回該符號或者該函數的優先級
int MyData::GetPriority() const{
return priority;
}
//返回類型名
string MyData::GetName()const {
return name;
}
//返回該類型名的值
double MyData::GetVal()const {
return value;
}
//判斷是符號,還是數字,還是函數
DataType MyData::GetType()const {
return type;
}
//生成一個MyData指針對象
MyData* CreateMyData(string name) {
MyData* mydata = new MyData(name);
return mydata;
}
//數字操作
void NumberOperate(vector<MyData*>::iterator it) {
//數字直接傳遞給後綴表達式
chain_suffix.push_back(*it);
}
//左括號操作
void LeftParenthesisOperate(vector<MyData*>::iterator it) {
//左括號進棧
stack.push_back(*it);
}
//右括號操作
void RightParenthesisOperate() {
while (stack.size() > 0) {
MyData* mydata = stack[stack.size() - 1];
//匹配到左括號
if (mydata->GetName().compare("(") == 0) {
stack.pop_back();//彈出左括號
return;
}
//符號傳遞給後綴表達式
chain_suffix.push_back(mydata);
//彈出非左括號的符號
stack.pop_back();
}
}
//運算符操作
void OpOperate(vector<MyData*>::iterator it) {
while (stack.size() > 0) {
MyData* mydata = stack[stack.size() - 1];
//棧頂元素優先級低
if ((*it)->GetPriority() < mydata->GetPriority() ) {
stack.push_back(*it);
return;
}
//符號傳遞給後綴表達式
chain_suffix.push_back(mydata);
//彈出非左括號的符號
stack.pop_back();
}
stack.push_back(*it);
}
//函數操作
void FuncOperate(vector<MyData*>::iterator it) {
OpOperate(it);
}
//通過函數計算後綴表達式的值
double FuncCaculate(double val,string name) {
//返回值
double ret = 0;
if (name.compare("sin") == 0) { //正弦
ret = sin(val);
}
else if (name.compare("cos") == 0) { //餘弦
ret = cos(val);
}
else if (name.compare("tan") == 0) { //正切
ret = tan(val);
}
else if (name.compare("asin") == 0) { //反正弦
ret = asin(val);
}
else if (name.compare("acos") == 0) { //反餘弦
ret = acos(val);
}
else if (name.compare("atan") == 0) { //反正切
ret = atan(val);
}
else if (name.compare("fabs") == 0) { //絕對值
ret = fabs(val);
}
else if (name.compare("sqrt") == 0) { //開平方
if (val < 0) {
cout << "[Error in <FuncCaculate>開方的底不能是負數]";
return -1;
}
ret = sqrt(val);
}
else if (name.compare("log") == 0) { //以e爲底的對數
if (val < 0) {
cout << "[Error in <FuncCaculate>以e爲底的對數的真數不能是負數]";
return -1;
}
ret = log(val);
}
else if (name.compare("log10") == 0) { //以10爲底的對數
if (val < 0) {
cout << "[Error in <FuncCaculate>以10爲底的對數的真數不能是負數]";
return -1;
}
ret = log10(val);
}
else if (name.compare("round") == 0) { //四捨五入
ret = round(val);
}
else if (name.compare("exp") == 0) { //e指數
ret = exp(val);
}
else if (name.compare("ceil") == 0) { //向上取整
ret = ceil(val);
}
else if (name.compare("floor") == 0) { //向下取整
ret = floor(val);
}
//else if (name.compare("rand") == 0) { //隨機數
//}
else {
cout << "[Error in <FuncCaculate>未匹配到函數]";
return -1;
}
return ret;
}
//通過運算符計算後綴表達式的值
double OpCaculate(double left, double right, string name) {
//返回值
double ret = 0;
if (name.compare("^") == 0) {
ret = pow(left, right);
}
else if (name.compare("/") == 0) {
if (right == 0) {
cout << "[Error in <OpCaculate>除數不能爲零!]";
return -1;
}
ret = left / right;
}
else if (name.compare("*") == 0) {
ret = left * right;
}
else if (name.compare("%") == 0) {
ret = (int)left % (int)right;
}
else if (name.compare("+") == 0) {
ret = left + right;
}
else if (name.compare("-") == 0) {
ret = left - right;
}
else {
cout << "[Error in <OpCaculate>未匹配到運算符]";
}
return ret;
}
//輸入一個字符串,並把它匹配放入中綴表達式中
void InputAstring() {
string str;
cout << "需要計算的式子爲:\n";
cin >> str;
/*
一次正則匹配
\\d+(\\.\\d+)?(e\\d+)? 數字,含小數和指數
[a-zA-Z\\_][a-zA-Z\\d\\_]* 函數,可以有下劃線和數字,開頭不能是數字
[\\+\\-\\*\\/\\^\\%\\(\\)] 符號
*/
regex reg("((\\d+(\\.\\d+)?(e\\d+)?)|([a-zA-Z\\_][a-zA-Z\\d\\_]*)|([\\+\\-\\*\\/\\^\\%\\(\\)]))");
for (sregex_iterator it(str.begin(), str.end(), reg), it_end; it != it_end; it++) {
cout << it->str() << " ";
chain_infix.push_back(CreateMyData(it->str()));
}
cout << "總共有 " << chain_infix.size() <<" 個token" <<endl;
}
//中綴表達式轉後綴表達式
void Infix2suffix() {
cout << "中綴表達式爲>\n";
cout << setw(6) << "符號" << setw(6) << "優先級" << setw(6) << "類型" << setw(6) << "值" << "\n";
for (auto i : chain_infix) {
cout << setw(6) << i->GetName() << setw(6) << i->GetPriority() << setw(6) << i->GetType() << setw(6) << i->GetVal() << "\n";
}
for (vector<MyData*>::iterator it = chain_infix.begin();it!=chain_infix.end(); ++it) {
DataType mytype = (*it)->GetType();
if ( mytype == FuncType) {
FuncOperate(it);
}
else if ( mytype == CharType) {
if((*it)->GetName().compare("(")==0) {
LeftParenthesisOperate(it);
}
else if ((*it)->GetName().compare(")") == 0) {
RightParenthesisOperate();
}
else {
OpOperate(it);
}
}
else if ( mytype == NumType) {
NumberOperate(it);
}
else {
cout << "[Error in <Infix2Suffix>"<<(*it)->GetName()<<" is OtherType]";
}
}
//彈出棧裏所有的符號
while (stack.size()>0) {
//取得棧頂元素
MyData* mydata = stack[stack.size() - 1];
//放入後綴表達式
chain_suffix.push_back(mydata);
//從棧中彈出
stack.pop_back();
}
//清空棧
//clear()只清空,但是不回收空間
vector<MyData*>().swap(chain_infix);
vector<MyData*>().swap(stack);
}
//後綴表達式求解
void CaculateSuffix() {
cout << "後綴表達式爲>\n";
cout << setw(6) << "符號"<< setw(6) << "優先級" << setw(6) << "類型" << setw(6) << "值" << "\n";
for (auto i : chain_suffix) {
cout << setw(6)<<i->GetName() << setw(6) << i->GetPriority() << setw(6) << i->GetType() << setw(6) << i->GetVal() << "\n";
}
auto chunk = [](double ret) {
//把double轉變爲string
stringstream buf;
buf << ret;
string dst;
buf >> dst;
//把結果壓入棧中
stack.push_back(CreateMyData(dst));
};
for (vector<MyData*>::iterator it = chain_suffix.begin();it!=chain_suffix.end(); ++it) {
DataType mytype = (*it)->GetType();
if (mytype == FuncType) {
//彈出唯一的操作數
MyData* oneData = stack[stack.size() - 1];
if (oneData == nullptr) {
cout << "[Error in <CaculateSuffix>容器是空的,函數不能計算返回值]";
}
stack.pop_back();
//根據函數名進行運算
double ret = FuncCaculate(oneData->GetVal(),(*it)->GetName() );
//把結果放入棧中
chunk(ret);
}
else if (mytype == CharType) {
//先彈出右操作數
MyData* rightData = stack[stack.size() - 1];
double rightVal = rightData->GetVal();
stack.pop_back();
//彈出左操作數
double leftVal = 0; //如果沒有左操作數,就是個負數,設爲零
if (stack.size() > 0) {
MyData* leftData = stack[stack.size() - 1];
leftVal = leftData->GetVal();
stack.pop_back();
}
//根據操作符進行運算
double ret = OpCaculate(leftVal, rightVal, (*it)->GetName());
//把結果放入棧中
chunk(ret);
}
else if (mytype == NumType) {
//數字直接入棧
stack.push_back(*it);
}
else {
cout << "[Error in <CaculateSuffix>" << (*it)->GetName() << " is OtherType]";
}
}
//把最後的結果返回出來
if (stack.size() == 1) {
MyData* mydata = stack[0];
cout << "\n計算結果爲>\n" << mydata->GetVal() << endl;
stack.pop_back();
}
else {
cout << "[Error in <CaculateSuffix>棧中元素還有" << stack.size() << "個]";
}
//清空棧
vector<MyData*>().swap(chain_suffix);
vector<MyData*>().swap(stack);
}
//main.cpp
#include"calc.h"
void main(void) {
cout << "===================Hello World====================" << endl;
char ch;
do {
InputAstring();
Infix2suffix();
CaculateSuffix();
cout << "繼續嗎?Y/N"<<endl;
cin >> ch;
} while (ch == 'y' || ch == 'Y');
system("pause");
}
效果演示
還存在的問題有
- 數字字符串轉換成double,計算完又轉變爲字符串,這種操作怕丟失信息
- 因爲是double之間進行的計算,數字不能太大
- 默認函數只能有一個參數
- 通過默認函數計算的部分,條件判斷太多了
如果引入自定義變量,引入賦值條件語句,對多參數的式子進行構造語法樹的操作……就能做個shell?或者是命令行語言了。