lang:計算器改進版本_默認函數_小數_指數

前言

之前的版本只能計算單個數字之間的加、減、乘、除、取餘、 乘方。這次首先對輸入的字符串進行正則匹配,分割之後放入棧中。還重新設計了新的優先級,並對中綴表達式和後綴表達式的轉換做了點修改。但是缺點也有,提供的默認函數,如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. 函數優先級爲1
  2. ^ 乘方優先級2
  3. * 乘 / 除 % 取餘優先級3
  4. + 加 - 減優先級4
  5. ()括號優先級5
  6. 其他符號優先級6
  7. 數字沒有優先級,設爲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");
	
}

效果演示
在這裏插入圖片描述

還存在的問題有

  1. 數字字符串轉換成double,計算完又轉變爲字符串,這種操作怕丟失信息
  2. 因爲是double之間進行的計算,數字不能太大
  3. 默認函數只能有一個參數
  4. 通過默認函數計算的部分,條件判斷太多了

如果引入自定義變量,引入賦值條件語句,對多參數的式子進行構造語法樹的操作……就能做個shell?或者是命令行語言了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章