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?或者是命令行语言了。

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