手把手教你写个简易计算器--栈的应用(C++)

版权声明:转载请注明出处,谢谢合作 https://blog.csdn.net/qq_40738840/article/details/85266457

程序使用范围

1) 运算数为实数
2) 运算符为+、-、*、/、(、)、#
3) 运算结果为实数

设计流程

主要分为三步
1,表达式预处理
2,建立运算符优先表
3,运算求值

1) 表达式预处理

文件中读取一行,去除所有空格,并在表达式首尾各添加一个符号‘#’,表示(作为起止标记)

//表达式预处理:从文件中读出表达式,同时去除所有空格、并在头尾各加一个字符‘#’ 
void InitExpression(string &s)
{
	//从文件中读取表达式,并将去除所有空格,在头尾各加一个‘#’字符的字符串存入s中 
	fstream inFile("测试表达式.txt",ios::in);
	if(!inFile)cout<<"文件打开失败!"<<endl;
	
	char ch;
	s += '#';
	while(true)
	{
		inFile>>ch;//去空格,换行读取 
		if(!inFile)break;//这两行位置不可换,否则最后一个字符会多读一次 
		s += ch;	 
	}
	s += '#';
	inFile.close(); //关闭文件,好习惯 
} 

文件测试表达式.txt内容

100.234 + 2.258 +(-1.43)*(-3)*(-2000.6)+(-9-4/2+3)*2/2-1-9/1/3/3 +   1 + 28*2*2/56/2*(-10.24)/100.43

2) 建立运算符优先表

四则运算法则

  • 先乘除,后加减
  • 先左后右
  • 先括号内,后括号外

文件运算优先级.txt的内容

+ - * / ( ) #
> > < < < > >
> > < < < > >
> > > > < > >
> > > > < > >
< < < < < = $
$ $ $ $ $ > >
< < < < < $ =
  • 第一行为运算符
  • 剩余7行是优先关系,其中$代表关系不存在/无意义;左右括号优先级相等,井号与井号优先级相等,井号作用类似括号

  • 从文件读取并以运算符ASCII码作为下标建立关系表
  • 因为运算符个数有限,所以可用下标映射,类似哈希映射,也与哈夫曼编码实现思想异曲同工
void CreateRelation(char relation[255][255])//直接传递二维数组等价于传址 
{
	fstream inFile("运算优先级.txt",ios::in);
	if(!inFile)cout<<"文件打开失败!"<<endl;
	
//	vector<int> optr;//存储7个运算符的ASCII码 
	string s;//从文件读取一行 
	getline(inFile,s);//读取第一行,操作符(运算符) 
	for(int i = 0; i < s.size(); i++)//将符号转换为数值 
	{
		if(s[i] == ' ')continue;//跳过空格 
		int a = s[i];//cout<<"s[i]:"<<s[i]<<"  a: "<<a<<endl;
		optr.push_back(a);//为何不能一边推入数值,一边输出???? 
	}
//	char relation[255][255];//存储运算符优先级,运算符的ASCII码作为下标 
	//将运算符关系存储于关系数组 
	for(int i = 0; i < optr.size(); i++)
	{
		getline(inFile,s);
		int k = 0;//计算s位置 
		for(int j = 0; j < optr.size(); j++)
		{
			if(s[k] == ' ')k++;//每次最多一个空格 
			relation[optr[i]][optr[j]] = s[k++];
		}
	}
	inFile.close();
} 

3) 运算求值

实数的处理

实数包含三个部分:符号位,整数部分,小数部分
符号位判断:当前为数值,前一位为“-”/“+”,前两位为“(”,是负数/正数

运算符号处理

假设运算符号a1在a2前,二者优先级共三种情况,分别对应不同处理

  • a1 < a2,a2压入符号栈
  • a1 = a2,弹出符号栈栈顶
  • a1 < a2,弹出符号栈栈顶a1,弹出数值栈两个元素b,a(注意顺序)与a1运算(a a1 b),得到的结果压入式数值栈

实数的三个部分处理时一定细心;运算符三个分支把握好

//该判断用得多,干脆封装为函数
//判断是否为操作数。是->true;不是->false 
bool IsOpnd(char ch)
{
	int a = ch - '0';
	if(a>=0 && a<=9)return true;
	else return false;
}
//表达式求值 
void EvaluateExpression()
{
	char relation[255][255];
	CreateRelation(relation);//创建运算优先表 
	
	string s;
	InitExpression(s);//表达式预处理 
	cout<<s;
	
	//========开始处理表达式============= 
	stack<double> opnd;//数值栈 
	stack<char>	optr;//运算符栈 
	optr.push(s[0]);//'#'压入,作为标记 
	
	int pos = 1;//记录s的位置 
	while(!optr.empty())
	{	int fix = 1;//符号位 
		if(IsOpnd(s[pos]))//是操作数 
		{
			double a = s[pos] - '0';//字符转化为ASCII码(整型) 
			//处理符号位:判断正负 
			if(pos>=3 && s[pos-2] == '(' && s[pos-1] == '-')//一元操作符(-19) 
			{
				fix = -1;
				optr.pop();
			}
			else if(pos>=3 && s[pos-2] == '(' && s[pos-1] == '+')
			{
				fix = 1;
				optr.pop();
			}
			//============处理数值 ================
			while(IsOpnd(s[pos+1]))//处理整数部分 
			{
				int t = s[pos+1] - '0';
				a = a*10 + t;
				pos++;
			}
			if(s[pos+1] == '.')//处理小数部分 
			{
				pos++;
				int count = 1;//计算到小数点的距离
				while(IsOpnd(s[pos+1]))
				{
					double t = s[pos+1] - '0';
					for(int i = 0; i < count; i++)
					{
						t = t/10;
					}
					a += t;
					count++;
					pos++; 
				}
			}
			a = a*fix;
//			cout<<a<<endl;
			opnd.push(a);//重新压入 
			pos++;
		}
		else//操作符 
		{
			char ch1,ch2,r;
			ch1 = optr.top();//栈顶运算符 
			ch2 = s[pos];
			
			int a1,a2;//转化为数值 
			a1 = ch1;
			a2 = ch2;
			
			r = relation[a1][a2];//ch1,ch2关系,注意二者顺序,在栈里的在前面!!! 
			if(r == '<')//ch1<ch2,运算符直接入栈
			{
				optr.push(ch2);
				pos++;
			} 
			else if(r == '=')
			{
				optr.pop();
				pos++;
			}
			else if(r == '>')
			{
				char tch = optr.top();
				optr.pop();
				
				double a,b;//注意出栈顺序 
				b = opnd.top();opnd.pop();
				a = opnd.top();opnd.pop();
				
				double result = 0;
				if(tch == '+')
				{
					result = a + b;
				}
				else if(tch == '-')
				{
					result = a - b;
				}
				else if(tch == '*')
				{
					result = a * b;
				}
				else if(tch == '/')
				{
					result = a / b;
				}
				opnd.push(result);//结果入数值栈 
				//不需要pos++,当前字符继续判断即可 
			}
		} 
	}
	cout<<endl<<"  结果:"<<opnd.top()<<endl;
} 

总结体会

  • 打开电脑前把要做的事想得有7分明白,这次做得不错,基本思路都已成熟,所以写出程序很快。为啥是7分明白呢?因为不足7分思路混乱,bug出现概率极高,而且难以调试,可谓磨刀不误砍柴工;为啥不是8分,9分甚至10分呢?一是几乎不可能把你第一次接触的问题想个透彻,二是太浪费时间了。中庸之道,带着7分理解,如此次我已设计好三个基本流程;在实际编码中发现之前未注意到的细节,写代码时才发现需要字符串处理,由于一开始为考虑实数处理,导致卡在实数处理上2个小时,oh天呐!这样bug少,时间短,效率极高。
  • 写代码就像盖房子,先有好的架构,再落实到一砖一瓦上,地基不稳,地动山摇。为了避免出现问题无从下手,没写一个功能都测试一下,依次迭代开发,效率较高
  • 从文本文件读出一行字符串是包括空格的,需要处理
  • 做有个字符串的题目,字符处理是最核心的,别以为他核心逻辑简单,但是字符处理逻辑复杂呀,而且千体千面,不想其他算法,模板稍微改改就成,这个得自己仔细分析,一种情况都漏不得
  • 不知是不是写代码时间持续太长,导致之前明明很清楚,写对的代码到后来自己又改错了。以后一定要适当休息,身体第一

完整源码

#include<iostream>
using namespace std;
#include<stack>
#include<string>
#include<fstream>
#include<vector>
vector<int> optr;
void CreateRelation(char relation[255][255])//直接传递二维数组等价于传址 
{
	fstream inFile("运算优先级.txt",ios::in);
	if(!inFile)cout<<"文件打开失败!"<<endl;
	
//	vector<int> optr;//存储7个运算符的ASCII码 
	string s;//从文件读取一行 
	getline(inFile,s);//读取第一行,操作符(运算符) 
	for(int i = 0; i < s.size(); i++)//将符号转换为数值 
	{
		if(s[i] == ' ')continue;//跳过空格 
		int a = s[i];//cout<<"s[i]:"<<s[i]<<"  a: "<<a<<endl;
		optr.push_back(a);//为何不能一边推入数值,一边输出???? 
	}
	for(int i = 0; i < optr.size(); i++)
	{
	//		cout<<optr[i]<<" ";
	}
//	char relation[255][255];//存储运算符优先级,运算符的ASCII码作为下标 
	//将运算符关系存储于关系数组 
	for(int i = 0; i < optr.size(); i++)
	{
		getline(inFile,s);
		int k = 0;//计算s位置 
		for(int j = 0; j < optr.size(); j++)
		{
			if(s[k] == ' ')k++;//每次最多一个空格 
			relation[optr[i]][optr[j]] = s[k++];
		}
	}
	inFile.close();
/*	for(int i = 0; i < optr.size(); i++)
	{
		for(int j = 0; j < optr.size(); j++)
		{
			cout<<relation[optr[i]][optr[j]]<<" ";
		}
		cout<<endl;
	}
*/	
} 
//判断是否为操作数。是->true;不是->false 
bool IsOpnd(char ch)
{
	int a = ch - '0';
	if(a>=0 && a<=9)return true;
	else return false;
}
//表达式预处理:从文件中读出表达式,同时去除所有空格、并在头尾各加一个字符‘#’ 
void InitExpression(string &s)
{
	//从文件中读取表达式,并将去除所有空格,在头尾各加一个‘#’字符的字符串存入s中 
	fstream inFile("测试表达式.txt",ios::in);
	if(!inFile)cout<<"文件打开失败!"<<endl;
	
	char ch;
	s += '#';
	while(true)
	{
		inFile>>ch;//去空格,换行读取 
		if(!inFile)break;//这两行位置不可换,否则最后一个字符会多读一次 
		s += ch;	 
	}
	s += '#';
	inFile.close(); //关闭文件,好习惯 
} 
//表达式求值 
void EvaluateExpression()
{
	char relation[255][255];
	CreateRelation(relation);//创建运算优先表 
	
	string s;
	InitExpression(s);//表达式预处理 
	cout<<s;
	
	//开始处理表达式 
	stack<double> opnd;//数值栈 
	stack<char>	optr;//运算符栈 
	optr.push(s[0]);//'#'压入,作为标记 
	
	int pos = 1;//记录s的位置 
	while(!optr.empty())
	{	int fix = 1;//符号位 
		if(IsOpnd(s[pos]))//是操作数 
		{
			double a = s[pos] - '0';//字符转化为ASCII码(整型) 
			//处理符号位:判断正负 
			if(pos>=3 && s[pos-2] == '(' && s[pos-1] == '-')//一元操作符(-19) 
			{
				fix = -1;
			//	a = -a;cout<<"-a:"<<a<<endl;
			//	pos++;
				optr.pop();
			}
			else if(pos>=3 && s[pos-2] == '(' && s[pos-1] == '+')
			{
				fix = 1;
			//	a = a;
			//	pos++;
				optr.pop();
			}
			//处理数值 
			while(IsOpnd(s[pos+1]))//处理整数部分 
			{
				int t = s[pos+1] - '0';
				a = a*10 + t;
				pos++;
			}
			if(s[pos+1] == '.')//处理小数部分 
			{
				pos++;
				int count = 1;
				while(IsOpnd(s[pos+1]))
				{
					double t = s[pos+1] - '0';
					for(int i = 0; i < count; i++)
					{
						t = t/10;
					}
				//	if(a < 0) t = -t; 
					a += t;
					count++;
					pos++; 
				}
			}
			a = a*fix;
//			cout<<a<<endl;
			opnd.push(a);//重新压入 
			pos++;
		}
		else//操作符 
		{
			char ch1,ch2,r;
			ch1 = optr.top();//栈顶运算符 
			ch2 = s[pos];
			
			int a1,a2;//转化为数值 
			a1 = ch1;
			a2 = ch2;
			
			r = relation[a1][a2];//ch1,ch2关系,注意二者顺序,在栈里的在前面!!! 
			if(r == '<')//ch1<ch2,运算符直接入栈
			{
				optr.push(ch2);
				pos++;
			} 
			else if(r == '=')
			{
				optr.pop();
				pos++;
			}
			else if(r == '>')
			{
				char tch = optr.top();
				optr.pop();
				
				double a,b;//注意出栈顺序 
				b = opnd.top();opnd.pop();
				a = opnd.top();opnd.pop();
				
				double result = 0;
				if(tch == '+')
				{
					result = a + b;
				}
				else if(tch == '-')
				{
					result = a - b;
				}
				else if(tch == '*')
				{
					result = a * b;
				}
				else if(tch == '/')
				{
					result = a / b;
				}
				opnd.push(result);//结果入数值栈 
				//不需要pos++,当前字符继续判断即可 
			}
		} 
	}
	cout<<endl<<"  结果:"<<opnd.top()<<endl;
} 
int main()
{
	EvaluateExpression();
	return 0;
} 

猜你喜欢

转载自blog.csdn.net/qq_40738840/article/details/85266457
今日推荐