C++做四則運算的MFC計算器(二)棧轉換和計算后綴表達式


上篇寫了MFC界面搭建,這篇寫實現計算。涉及到數據結構,對新手很不友好。

 

一些園友在參考本文進行實現時遇到一些問題,程序有些老了,沒有進行修正,源碼在gitee可下<倉庫>Fork me on Gitee。程序程序最后處理CString和char[]有些問題,VS2017可以正常處理,有些版本的IDE不支持這里的處理方法,需要了解CString和 char *之間的轉換,作為一個參考方法,博客內有再提到這個。

 

這雖然是MFC程序,但是能靈活地分離后台代碼,自行構建控制台程序,本文的程序一開始只是個“黑框框”程序,后來把代碼包裝進了MFC。代碼里有些預處理宏定義等沒有加上,需要根據需求改動一下。

上篇文章鏈接:C++做四則運算的MFC計算器(一)MFC界面創建 

概要:

  1. 中綴表達式與后綴表達式
  2. 棧的相關實現
  3. 用棧將中綴表達式轉換成后綴表達式
  4. 用棧計算后綴表達式 
  5. 等號按鈕功能,顯示計算結果

中綴表達式與后綴表達式

中綴:(60-20)/(5-1)。小學就學的東西

后綴:60 20 – 5 1 - /,為增加可讀性,以“#”做分隔符,60#20#-#5#1#-#/。

后綴計算:從左到右遇到符號就計算符號左邊的兩個數並寫上新值,即優先的運算符相對在左。上例中遇到第一個‘-’算60-20得40,遇到第二個“-”計算5-1得4,遇到“/”計算30/3的10,結果是10。

對比:中綴式人看起來方便,后綴式沒有括號,計算順序從前到后,用計算機操作起來的邏輯簡單。

棧的相關實現

輸入的值都是字符,所以需要一個字符結構的棧;在計算數時還需要一個處理數字結構的棧。

字符和數值的棧結構體分別命名SqStack和SqStackN。

然后實現棧初始化、進棧、出棧等棧的操作函數。

在工程中,同時把棧結構體和操作函數聲明在新建的頭文件xxx.h中,或者工程中其他頭文件如stdafx.h,新建一個cpp文件實現函數體。

這里的棧和操作函數是自己動手定義的,可以直接用STL中的棧,使用比較方便。

棧內數據用的是 char ,在最后處理CString時可能會因IDE不支持導致出錯,需要了解CString與char *之間的轉換,我找了一篇比較詳細的博客CString用法總結

 1 struct SqStack {
 2     char data[maxsize];
 3     int top;
 4 };
 5 struct SqStackN {
 6     double data[maxsizen];
 7     int top;
 8 };
 9 //棧操作函數—字符
10 void initStack(SqStack *&s);
11 bool Push(SqStack *&s, char e);
12 bool Pop(SqStack *&s, char &e);
13 bool GetTop(SqStack *s, char &e);
14 void DestroyStack(SqStack *&s);
15 bool StackEmpty(SqStack *s);
16 //棧操作函數—數字
17 void initStack(SqStackN *&s);
18 bool Push(SqStackN *&s, double e);
19 bool Pop(SqStackN *&s, double &e);
20 bool GetTop(SqStackN *s, double &e);
21 void DestroyStack(SqStackN *&s);
22 bool StackEmpty(SqStackN *s);
23 
24 //后綴表達式轉換函數
25 void trans(char* exp, char postexp[]);
26 //計算后綴表達式函數
27 double calculate(char* postexp);
頭文件主要聲明
 1 void initStack(SqStack *&s) {
 2     s = new SqStack();
 3     s->top = -1;
 4 }
 5 bool Push(SqStack *&s, char e) {
 6     if (s->top == maxsize-1)
 7         return false;
 8     s->top++;
 9     s->data[s->top] = e;
10     return true;
11 }
12 bool Pop(SqStack *&s, char &e) {
13     if (s->top == -1)
14         return false;
15     e = s->data[s->top];
16     s->top--;
17     return true;
18 }
19 bool GetTop(SqStack *s, char &e) {
20     if (s->top == -1)
21         return false;
22     e = s->data[s->top];
23     return true;
24 }
25 void DestroyStack(SqStack *&s) {
26     free(s);
27 }
28 bool StackEmpty(SqStack *s) {
29     return (s->top == -1);
30 }
31 
32 void initStack(SqStackN *& s){
33     s = new SqStackN();
34     s->top = -1;
35 }
36 
37 bool Push(SqStackN *& s, double e)
38 {
39     if (s->top == maxsizen - 1)
40         return false;
41     s->top++;
42     s->data[s->top] = e;
43     return true;
44 }
45 
46 bool Pop(SqStackN *& s, double & e)
47 {
48     if (s->top == -1)
49         return false;
50     e = s->data[s->top];
51     s->top--;
52     return true;
53 }
54 
55 bool GetTop(SqStackN * s, double & e)
56 {
57     if (s->top == -1)
58         return false;
59     e = s->data[s->top];
60     return true;
61 }
62 
63 void DestroyStack(SqStackN *& s){
64     free(s);
65 }
66 
67 bool StackEmpty(SqStackN * s)
68 {
69     return (s->top == -1);
70 }
cpp函數定義

 

用棧將中綴表達式轉換成后綴表達式

從左向右掃描中綴,遇到數字就添加到后綴中,遇到運算符進行棧處理,而棧的處理依賴於運算符優先級,優先級高的靠近棧頂,保證在后綴式中先運算的運算符靠前。結束后后綴式仍是字符數組。

寫個函數tans(),有2個參數char * exp和char postexp[ ],

先初始化一個字符棧指針s,char e用來操作棧頂元素,int p作為postexp數組的下標。用循環掃描exp

while (*exp != '\0') {switch(*exp){case:case:case:default}......}

掃描到數字字符時直接加到后綴式中,並加上 ‘ # ’ 以分割。

其他情況無非是+、-、*、/、(、)這6個符號。

(、)優先級最高,在括號之間的運算符一定比括號之外的優先運算,遇到 ‘ ( ’ 即進棧,遇 ‘ ) ’ 即出棧棧頂元素直到出來的是 ‘ ( ’ 。因此棧中不會有 ‘ ) ’ 。

*、/優先級其次,先判斷棧頂是什么,棧頂是*、/則將其出棧到后綴式,棧頂是+、-則將 ‘ * ’ 或 ‘ / ’ 進棧,保證后綴中乘和除的優先運算。

+、-優先級最低,這是棧頂元素不管是+-*/都出棧至后綴式,但棧頂是 ‘ ( ’ 時就不需要出棧,‘ ( ’可以看作是一個新的運算起點,將+或-進棧即可。

exp掃描后棧可能還會有運算符,將剩下的都出棧至后綴式。再為后綴式加結束標識 ‘\0’ ,銷毀棧釋放空間。

 1 void trans(char* exp,char postexp[]) {  2     char e;  3     SqStack *s;    initStack(s);    // 即SqStack s = new SqStacck()
 4     int p = 0;//postexp的下表
 5     while (*exp != '\0') {  6         switch (*exp)  7  {  8         case '+':  9         case '-': 10             while (!StackEmpty(s)) { 11  GetTop(s, e); 12                 if (e != '(') { 13                     postexp[p++] = e; 14  Pop(s, e); 15  } 16                 else
17                     break; 18  } 19             Push(s, *exp); 20             exp++;break; 21         case '*': 22         case '/': 23             while (!StackEmpty(s)) { 24  GetTop(s, e); 25                 if (e == '*' || e == '/') { 26                     postexp[p++] = e; 27  Pop(s, e); 28  } 29                 else
30                     break; 31  } 32             Push(s, *exp); 33             exp++;break; 34         case '(': 35             Push(s, *exp); 36             exp++;break; 37         case ')': 38  Pop(s, e); 39             while (e != '(') { 40                 postexp[p++] = e; 41  Pop(s, e); 42  } 43             exp++;break; 44         default: 45             while (*exp >= '0'&&*exp <= '9') { 46                 postexp[p++] = *exp; 47                 exp++; 48  } 49             postexp[p++] = '#'; 50             break; 51  } 52  } 53     while (!StackEmpty(s)) { 54  Pop(s, e); 55         postexp[p++] = e; 56  } 57     postexp[p] = '\0'; 58  DestroyStack(s); 59 }
轉換函數trans()

棧計算后綴表達式 

這一功能相對簡單些,掃描后綴式,

遇到表示數字的字符時,例如 ‘ 6 ’ ,利用它與 ‘ 0 ’ 編碼之間的差得到數字,‘ 6 ’ - ‘ 0 ’ = 6 ,乘10實現位數值。得到的數值放入棧里。

遇到運算符時直接出棧兩個元素,此時這兩個元素一定是數字,第一個出棧的做運算符右值,第二個做左值,順序不能反,結果還要入棧以進行下一步運算。

遇到 ‘ / ’ 時還要判斷棧頂是否是0,被除數可不能是0。

最后棧中就是運算結果,出棧作為返回值。

 1 double calculate(char* postexp) {  2     double a, b,c,d,e;  3     SqStackN *o; initStack(o);  4     while (*postexp != '\0') {  5         switch (*postexp)  6  {  7         case '+':  8  Pop(o, a);  9  Pop(o, b); 10             c = b + a; 11  Push(o, c); 12             break; 13         case '-': 14  Pop(o, a); 15  Pop(o, b); 16             c = b - a; 17  Push(o, c); 18             break; 19         case '*': 20  Pop(o, a); 21  Pop(o, b); 22             c = b * a; 23  Push(o, c); 24             break; 25         case '/': 26  Pop(o, a); 27  Pop(o, b); 28             if (a != 0) { 29                 c = b / a; 30  Push(o, c); 31                 break; 32  } 33             else { 34                 exit(0); 35  } 36             break; 37         default: 38             d = 0; 39             while (*postexp >= '0'&&*postexp <= '9') { 40                 d = d * 10 + (*postexp - '0'); 41                 postexp++; 42  } 43  Push(o, d); 44             break; 45  } 46         postexp++; 47  } 48  GetTop(o, e); 49  DestroyStack(o); 50     return e; 51 }
計算后綴式函數

等號按鈕功能-計算結果顯示

 運算功能已經實現了,但是要講功能綁定到 ‘ = ’ 還有最后一道坎

MFC文本編輯框的類型是CString,要想轉換成char[]有點讓人頭大。

需要幾個變量,輸入的算式char exp[50],后綴式char postexp[50],運算結果result。

CString轉換為char[]直接上代碼,網上找的: ::wsprintfA(exp, "%ls", (LPCTSTR)editv); 

double轉CString代碼,CString的格式化: resultv.Format(_T("%.5lf"), result); 

目前發現處理CString有些問題,VS2017可以正常處理,舊點版本的IDE不支持這里的處理方法。

 1 void CMFCcalculationDlg::OnBnClickedButton19()  2 {  3     // TODO: 等號按鈕
 4     char exp[50];  5     char postexp[50];  6     double result;  7     UpdateData(true);  8     ::wsprintfA(exp, "%ls", (LPCTSTR)editv);  9  trans(exp, postexp); 10     result = calculate(postexp); 11     resultv.Format(_T("%.5lf"), result); 12     //resultv.Format(TEXT("%lf\n%.2lf"), result);
13     UpdateData(false); 14 }
等號按鈕功能

 

運行結果:

 

程序在未發布前比較大,100多M,包含了很多不用的文件,在資源管理器里還是隱藏的。發布后的程序只有幾M。

程序發布:將Debug改成Release,運行即發布,之后項目同級目錄里有Release文件夾,里面就是你的應用程序。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM