1. 程式人生 > >手把手教你寫個簡易計算器--棧的應用(C++)

手把手教你寫個簡易計算器--棧的應用(C++)

程式使用範圍

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