上篇寫了MFC界面搭建,這篇寫實現計算。涉及到數據結構,對新手很不友好。
一些園友在參考本文進行實現時遇到一些問題,程序有些老了,沒有進行修正,源碼在gitee可下<倉庫>。程序程序最后處理CString和char[]有些問題,VS2017可以正常處理,有些版本的IDE不支持這里的處理方法,需要了解CString和 char *之間的轉換,作為一個參考方法,博客內有再提到這個。
這雖然是MFC程序,但是能靈活地分離后台代碼,自行構建控制台程序,本文的程序一開始只是個“黑框框”程序,后來把代碼包裝進了MFC。代碼里有些預處理宏定義等沒有加上,需要根據需求改動一下。
上篇文章鏈接:C++做四則運算的MFC計算器(一)MFC界面創建
概要:
- 中綴表達式與后綴表達式
- 棧的相關實現
- 用棧將中綴表達式轉換成后綴表達式
- 用棧計算后綴表達式
- 等號按鈕功能,顯示計算結果
中綴表達式與后綴表達式
中綴:(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 }
用棧將中綴表達式轉換成后綴表達式
從左向右掃描中綴,遇到數字就添加到后綴中,遇到運算符進行棧處理,而棧的處理依賴於運算符優先級,優先級高的靠近棧頂,保證在后綴式中先運算的運算符靠前。結束后后綴式仍是字符數組。
寫個函數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 }
棧計算后綴表達式
這一功能相對簡單些,掃描后綴式,
遇到表示數字的字符時,例如 ‘ 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文件夾,里面就是你的應用程序。