手把手教你寫個簡易計算器--棧的應用(C++)
阿新 • • 發佈:2019-01-12
程式使用範圍
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