前言
之前的版本只能计算单个数字之间的加、减、乘、除、取余、 乘方。这次首先对输入的字符串进行正则匹配,分割之后放入栈中。还重新设计了新的优先级,并对中缀表达式和后缀表达式的转换做了点修改。但是缺点也有,提供的默认函数,如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?或者是命令行语言了。