淄博桓臺(tái)網(wǎng)站建設(shè)報(bào)價(jià)湘潭關(guān)鍵詞優(yōu)化公司
1、本篇要實(shí)現(xiàn)的內(nèi)容
最近,大家討論計(jì)算器的實(shí)現(xiàn)比較熱,今天我也來(lái)用C++和Visual Studio實(shí)現(xiàn)一個(gè)計(jì)算器的小程序。這里使用逆波蘭算法,能夠根據(jù)當(dāng)前用戶(hù)輸入的算式表達(dá)式字符串,計(jì)算出所要的結(jié)果,算式字符串可以包括加、減、乘、除和括號(hào),支持整數(shù)、小數(shù),鼠標(biāo)和鍵盤(pán)均可操作,實(shí)現(xiàn)了一個(gè)較為經(jīng)典的計(jì)算器功能。后期如果有時(shí)間我們?cè)賹?shí)現(xiàn)一些更多的計(jì)算器功能。本篇實(shí)現(xiàn)的效果如下:
2、設(shè)計(jì)目標(biāo)
我們今天想制作一個(gè)計(jì)算器,需要基本上能達(dá)到日常使用的需求。首先它得有可操作的圖形窗口界面,它要能夠滿(mǎn)足我們一些基本的計(jì)算需求,如整數(shù)和小數(shù)的加、減、乘、除,順便再把括號(hào)功能附加上。同時(shí)我們?cè)谠O(shè)計(jì)的時(shí)候,還允許用戶(hù)輸入算式表達(dá)式字符串,程序能根據(jù)用戶(hù)輸入算式表達(dá)式字符串,經(jīng)過(guò)一些智能糾錯(cuò)后,對(duì)糾錯(cuò)后的算式表達(dá)式進(jìn)行實(shí)時(shí)計(jì)算,并最終顯示出結(jié)果。
2.1 、運(yùn)行環(huán)境
操作系統(tǒng):Windows10操作系統(tǒng)
編譯環(huán)境:Microsoft Visual Studio 2010(VC6.0也可以直接編譯運(yùn)行)
其它事項(xiàng):源代碼僅僅包括一個(gè)cpp源文件,新建項(xiàng)目可直接編譯運(yùn)行,無(wú)需在資源編輯器中額外創(chuàng)建按鍵、顯示框等控件資源。
2.2、實(shí)現(xiàn)圖形化界面
首先計(jì)算器要方便使用,我們必須為它創(chuàng)建一個(gè)友好的圖形界面。我們首先為他創(chuàng)建一個(gè)應(yīng)用窗口,并為窗口添加相應(yīng)的控件??亻g最主要包括兩大部分,一部分是用于用戶(hù)輸入的響應(yīng)按鍵,另一部分是用于反饋用戶(hù)輸入和計(jì)算結(jié)果的顯示控件。為了簡(jiǎn)化項(xiàng)目,我們這里采用系統(tǒng)CreateWindow函數(shù)創(chuàng)建的按鍵BUTTON控件和STATIC控件,分別來(lái)響應(yīng)用戶(hù)輸入和輸出。圖形的區(qū)域分布如下:
2.3、實(shí)現(xiàn)字符串算式自動(dòng)識(shí)別計(jì)算
通過(guò)字符串算式自動(dòng)識(shí)別數(shù)學(xué)表達(dá)式有兩個(gè)優(yōu)點(diǎn)。第一個(gè)優(yōu)點(diǎn),可以方便用戶(hù)隨時(shí)校對(duì)自己輸入算式表達(dá)式的正確性。在計(jì)算時(shí)我不僅僅需要看到的是計(jì)算后的得數(shù),有時(shí)候我還需要看到我們已經(jīng)輸入的算術(shù)表達(dá)式,方便我校對(duì)輸入的式子是否正確,如發(fā)現(xiàn)錯(cuò)誤還可以及時(shí)修改。第二個(gè)優(yōu)點(diǎn),字符串算式表達(dá)式可以考慮到算式計(jì)算的優(yōu)先級(jí)。在普通沒(méi)有字符串表達(dá)式的計(jì)算器中,我們每輸入一個(gè)算術(shù)符號(hào)和數(shù)字,就必須要計(jì)算出這一步的結(jié)果。如此循環(huán)操作,再往下繼續(xù)輸入運(yùn)算符號(hào)和數(shù)字,屏幕只顯示當(dāng)前的結(jié)果。那么這樣就勢(shì)必?zé)o法考慮到加減乘除運(yùn)算規(guī)則,只能根據(jù)用戶(hù)輸入算式的先后順序計(jì)算,更沒(méi)有辦法考慮到括號(hào)的優(yōu)先運(yùn)算。那么字符串算數(shù)表達(dá)式就可以完美解決這個(gè)問(wèn)題,這里還要用到逆波蘭算法。
在這個(gè)算式中我們需要先計(jì)算3*13=39的乘法,在計(jì)算12+39=51的加法。
2.4、支持加、減、乘、除和括號(hào)
由于使用了逆波蘭算法,這里運(yùn)算我們支持加減乘除,還添加了對(duì)括號(hào)的支持。我們采用了字符串算式格式,我們可以方便的對(duì)加減乘除和括號(hào)的運(yùn)算規(guī)則進(jìn)行支持。因?yàn)樵谌粘5倪\(yùn)算中,如果拿著計(jì)算器還需要自己去考慮一個(gè)算式的運(yùn)算順序的話(huà),會(huì)是一個(gè)很糟糕的體驗(yàn)。
2.5、實(shí)時(shí)更新運(yùn)算結(jié)果
我們?cè)谖覀冊(cè)谥谱饔?jì)算機(jī)前期構(gòu)想的時(shí)候,借鑒了手機(jī)上自帶計(jì)算器功能的一些創(chuàng)意,用戶(hù)每輸入一個(gè)字符都會(huì)更新并影響到最終結(jié)果。在使用計(jì)算器的時(shí)候,當(dāng)用戶(hù)每輸入一個(gè)數(shù)字或者符號(hào)時(shí),計(jì)算器都會(huì)根據(jù)當(dāng)前已經(jīng)輸入的算式表達(dá)式,進(jìn)行智能分析,預(yù)估出用戶(hù)可能需要的結(jié)果,隨即實(shí)時(shí)計(jì)算出結(jié)果并顯示。
2.6、智能運(yùn)算符號(hào)校驗(yàn)
我們是采用對(duì)字符串進(jìn)行逆波蘭法計(jì)算,并且是實(shí)時(shí)(每輸入一個(gè)數(shù)字或字符都會(huì)影響到結(jié)果)計(jì)算,因此對(duì)算式字符串的規(guī)范性檢測(cè)要求較高。但是我們?nèi)粘T谳斎胱址磉_(dá)式的時(shí)候,難免會(huì)存在一些手誤,比如說(shuō)連續(xù)輸入兩個(gè)乘號(hào)等等,那么這類(lèi)的錯(cuò)誤操作就需要我們用用戶(hù)輸入邏輯去加以規(guī)范或限制。同時(shí)還有用戶(hù)在輸入括號(hào)時(shí),表達(dá)式中的左右括號(hào)數(shù)量不一致等問(wèn)題,將會(huì)導(dǎo)致計(jì)算出現(xiàn)錯(cuò)誤。我們這里通過(guò)輸入邏輯檢測(cè)解決了用戶(hù)輸入表達(dá)式的規(guī)范性。
2.7、錯(cuò)誤判斷提示
在遇到除數(shù)為零的特殊情況時(shí),我們需要在結(jié)果中輸出錯(cuò)誤提示,否則計(jì)算會(huì)出現(xiàn)意外。如下圖:
2.8、支持整數(shù)、小數(shù)運(yùn)算
這里我們要雙精度數(shù)據(jù)類(lèi)型進(jìn)行計(jì)算,確保計(jì)算的準(zhǔn)確性。對(duì)小數(shù)的計(jì)算是我們?nèi)粘I钪胁豢缮俚?#xff0c;部分計(jì)算器并沒(méi)有增加對(duì)小數(shù)的支持。本次在程序設(shè)計(jì)的開(kāi)始,就考慮到了這一點(diǎn)。這里包括對(duì)有限小數(shù)的計(jì)算,包括對(duì)循環(huán)小數(shù)的計(jì)算,以及無(wú)限循環(huán)小數(shù)結(jié)果的顯示邏輯。
2.9、使用逆波蘭算法計(jì)算數(shù)學(xué)表達(dá)式
一. 波蘭式(前綴表達(dá)式)
波蘭邏輯學(xué)家J.Lukasiewicz于1929年提出的表示表達(dá)式的一種方式,即二元運(yùn)算符至于運(yùn)算數(shù)之前的一種表達(dá)方式。
二.中綴表達(dá)式
普通的表示表達(dá)式的一種方法,將二元運(yùn)算符置于運(yùn)算數(shù)中間,也是大多數(shù)情況下使用的一種方法。
三.逆波蘭式(后綴表達(dá)式)
與波蘭式相反,是二元運(yùn)算符置于運(yùn)算數(shù)之后的一種表達(dá)方式。每一運(yùn)算符都置于其運(yùn)算對(duì)象之后,故稱(chēng)為后綴表示。
三種表達(dá)式的形象實(shí)例如下:
逆波蘭式的應(yīng)用——算術(shù)表達(dá)式求值
逆波蘭式,也稱(chēng)逆波蘭記法(Reverse Polish Notation)。在數(shù)據(jù)結(jié)構(gòu)中,使用棧的概念完成表達(dá)式的求值操作,在計(jì)算機(jī)系統(tǒng)處理表達(dá)式的計(jì)算過(guò)程中,將中綴表達(dá)式轉(zhuǎn)換為后綴表達(dá)式的形式進(jìn)行解析轉(zhuǎn)換并實(shí)施計(jì)算,這就是逆波蘭算法的應(yīng)用。
具體實(shí)現(xiàn)方法大致為:
- 設(shè)兩個(gè)棧,操作數(shù)棧和運(yùn)算符棧;
- 操作數(shù)依次入操作數(shù)棧;
- 運(yùn)算符入棧前與運(yùn)算符的棧頂運(yùn)算符比較優(yōu)先級(jí);
- 優(yōu)先級(jí)高于棧頂運(yùn)算符,壓入棧,讀入下一個(gè)符號(hào);
- 優(yōu)先級(jí)低于棧頂運(yùn)算符,棧頂運(yùn)算符出棧,操作數(shù)棧退出兩個(gè)操作數(shù),進(jìn)行運(yùn)算,結(jié)果壓入操作數(shù)棧;
- 優(yōu)先級(jí)相等,左右括號(hào)相遇,棧頂運(yùn)算符出棧即可;
- 后綴表達(dá)式讀完,棧頂為運(yùn)算結(jié)果。
2.10、支持背景圖片
程序設(shè)計(jì)了一個(gè)簡(jiǎn)單的游戲背景設(shè)定,程序當(dāng)前文件夾中放置名為bg.bmp的圖片文件后,程序會(huì)自動(dòng)加載并居中顯示背景圖片,大家可以放上自己喜歡的背景圖片。
3、源碼下載
該源碼可以在VS2010和VC6.0中無(wú)差異運(yùn)行,因此就上傳了兩個(gè)版本的源碼,方便運(yùn)行。
3.1、VS2010源碼下載
CSDN下載地址:Calculator20241207-15-vs2010.rar
3.2、VC6.0源碼下載
CSDN下載地址:Calculator20241207-15-vc6.0.rar
4、源代碼實(shí)現(xiàn)過(guò)程
我們根據(jù)實(shí)現(xiàn)功能的不同,可以大致將整個(gè)項(xiàng)目分為以下各個(gè)模塊。
4.1、鏈表?xiàng)5膶?shí)現(xiàn)
由于逆波蘭法會(huì)要用到棧操作,我們預(yù)先定義一個(gè)鏈棧,在字符串表達(dá)式計(jì)算過(guò)程中會(huì)頻繁出棧和進(jìn)棧,已經(jīng)棧的初始化和銷(xiāo)毀,要注意內(nèi)存泄露。
//加載系統(tǒng)頭文件#include "windows.h"#include "stdio.h"#include "math.h"//節(jié)點(diǎn)統(tǒng)計(jì)數(shù)字int st_StackNodeNum=0;//鏈棧template<typename Type>struct Stack
{Type num;Stack<Type>* ptNext;};//初始化棧template<typename Type>void InitStack(Stack<Type>*& Node)
{Node = (Stack<Type>*)malloc(sizeof(Stack<Type>));Node->ptNext = NULL;st_StackNodeNum++;}//頭插法入棧template<typename Type>void PushStack(Stack<Type>*& Node, Type value)
{Stack<Type>* pt = (Stack<Type>*)malloc(sizeof(Stack<Type>));pt->num = value;pt->ptNext = Node->ptNext;Node->ptNext = pt;st_StackNodeNum++;}//頭插法出棧template<typename Type>void PopStack(Stack<Type>*& Node, Type& value)
{Stack<Type>* pt = Node->ptNext;value = pt->num;Node->ptNext = pt->ptNext;delete pt;st_StackNodeNum--;}//頭插法出棧template<typename Type>void DestroyStack(Stack<Type>*& Node)
{if(Node->ptNext == NULL){delete Node;Node=NULL;st_StackNodeNum--;}}//判斷棧是否為空,除去沒(méi)有存數(shù)據(jù)的首個(gè)節(jié)點(diǎn)外template<typename Type>bool IsStackEmpty(Stack<Type>* Node)
{return Node->ptNext == NULL;}//獲取棧頂部節(jié)點(diǎn)的數(shù)據(jù)template<typename Type>Type GetStackTopValue(Stack<Type>* Node)
{if(Node->ptNext !=NULL){return Node->ptNext->num;}else{return 0;}}
4.2、字符串操作函數(shù)
在字符表達(dá)式的輸入和處理過(guò)程中,會(huì)遇到一些必須的字符處理函數(shù),我們?cè)谶@里定義。
//省略掉數(shù)字的小數(shù)點(diǎn)后末尾多余的零void TrimBackZero(char *szString)
{//標(biāo)記小數(shù)點(diǎn)的位置int iDotPos=-1;//先找到小數(shù)點(diǎn)的位置for(int i=0;i<lstrlen(szString);i++){if(szString[i]=='.'){iDotPos=i;break;}}//尋找末尾多余的零for(int j=lstrlen(szString)-1;j>=iDotPos;j--){if(szString[j]=='.' || szString[j]=='0'){szString[j]='\0';}else{break;}}}//獲取字符串中某個(gè)字符的個(gè)數(shù)int GetCharAmount(char *szString,char sign)
{int iAmount=0;for(int i=0;i<lstrlen(szString);i++){if(szString[i]==sign)iAmount++;}return iAmount;}//判斷是否為數(shù)字bool IsNumber(char *szString)
{if(strcmp(szString,"0")==0)return true;if(strcmp(szString,"1")==0)return true;if(strcmp(szString,"2")==0)return true;if(strcmp(szString,"3")==0)return true;if(strcmp(szString,"4")==0)return true;if(strcmp(szString,"5")==0)return true;if(strcmp(szString,"6")==0)return true;if(strcmp(szString,"7")==0)return true;if(strcmp(szString,"8")==0)return true;if(strcmp(szString,"9")==0)return true;return false;}//判斷是否為運(yùn)算符號(hào)bool IsOperator(char *szString)
{if(strcmp(szString,"+")==0)return true;if(strcmp(szString,"-")==0)return true;if(strcmp(szString,"*")==0)return true;if(strcmp(szString,"/")==0)return true;return false;}
4.3、計(jì)算器類(lèi)
為了實(shí)現(xiàn)計(jì)算器的各個(gè)功能,我們集成到一個(gè)計(jì)算器類(lèi)中進(jìn)行操作。
//按鍵最大數(shù)量#define BUTTONMAXNUM 20//計(jì)算器類(lèi)class Calculator
{public://用于保存算式表達(dá)式字符串char szExpression[1024];//用于保存經(jīng)過(guò)校驗(yàn)的算式表達(dá)式字符串char szCheckedExpression[1024];//用于保存計(jì)算結(jié)果的字符串char szResult[1024];//控件字體設(shè)置HFONT hCtlFont;//用于存儲(chǔ)雙精度格式的結(jié)果double ResultDate;//標(biāo)記是否出現(xiàn)錯(cuò)誤bool tagError;//記錄錯(cuò)誤信息char szErrorMessage[1024];//背景圖片HBITMAP hBackGroundBitmap;public:Calculator();~Calculator();//初始化,用于創(chuàng)建按鍵控件和顯示控件void Initialize(HWND hWnd);//相應(yīng)鍵盤(pán)輸入轉(zhuǎn)換成統(tǒng)一的指令(鼠標(biāo)點(diǎn)擊按鍵)void OnCommand(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);//相應(yīng)鍵盤(pán)輸入轉(zhuǎn)換成統(tǒng)一的指令(數(shù)字按鍵和運(yùn)算符號(hào)按鍵)void OnChar(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);//相應(yīng)鍵盤(pán)輸入轉(zhuǎn)換成統(tǒng)一的指令(其他特殊按鍵)void OnKeyDown(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);//根據(jù)用戶(hù)的輸入指令進(jìn)行相應(yīng)的處理void OnExcuteString(HWND hWnd,char *szCommand);//屏幕顯示內(nèi)容void OnPaint(HWND hWnd,HDC hDC);//根據(jù)字符串計(jì)算結(jié)果double GetResultValueByString();//計(jì)算分步結(jié)果void CalValue(Stack<double> *&ptNumStack,Stack<char> *&ptOperatorStack);//逆波蘭算法實(shí)現(xiàn)double Polish(char *String, int len);};//自定義計(jì)算器類(lèi)實(shí)例Calculator Calculators;Calculator::Calculator()
{hCtlFont=NULL;strcpy(szExpression,"");strcpy(szCheckedExpression,"");strcpy(szResult,"");ResultDate=0;tagError=false;strcpy(szErrorMessage,"");hBackGroundBitmap=NULL;hBackGroundBitmap=(HBITMAP)LoadImage(NULL,"bg.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);}Calculator::~Calculator()
{//刪除字體資源DeleteObject(hCtlFont);}
4.3.1、初始化及界面初始化
我們這里分別采用系統(tǒng)CreateWindow函數(shù)創(chuàng)建的按鍵BUTTON控件和STATIC控件,分別來(lái)響應(yīng)用戶(hù)輸入和輸出。
void Calculator::Initialize(HWND hWnd)
{//控件字體設(shè)置HFONT hCtlFont=CreateFont(22,0,0,0,1000,0,0,0,0,0,0,PROOF_QUALITY,0,"宋體");//獲取窗口的大小RECT tempClientRect;GetClientRect(hWnd,&tempClientRect);//自定義按鍵的文字標(biāo)題char szButtonTitle[BUTTONMAXNUM][1024]={".","0","C","+","1","2","3","-","4","5","6","*","7","8","9","/","(",")","DEL","="};//創(chuàng)建按鍵控件,并設(shè)置按鍵的位置和標(biāo)題for(int i=0;i<BUTTONMAXNUM;i++){//設(shè)置按鍵的寬和高int w=60,h=35,gap=10;//設(shè)置按鍵的坐標(biāo)位置int x=10+(i%4)*(w+gap),y=tempClientRect.bottom-h-gap-(i/4)*(h+gap);//創(chuàng)建按鍵子控件CreateWindow("BUTTON",szButtonTitle[i],WS_CHILD|WS_VISIBLE|WS_CLIPCHILDREN|WS_CLIPCHILDREN|WS_CLIPSIBLINGS,x,y,60,35,hWnd,(HMENU)i,NULL,NULL);//設(shè)置字體記大小SendMessage(GetDlgItem(hWnd,i),WM_SETFONT,(WPARAM)hCtlFont,1);}//創(chuàng)建顯示子控件,算式顯示屏幕CreateWindowEx(WS_EX_CLIENTEDGE,"STATIC","",WS_CHILD|WS_VISIBLE|SS_RIGHT|SS_CENTERIMAGE,10,10,tempClientRect.right-20,50,hWnd,(HMENU)51,NULL,NULL);//創(chuàng)建顯示子控件,結(jié)果顯示屏幕CreateWindowEx(WS_EX_CLIENTEDGE,"STATIC","",WS_CHILD|WS_VISIBLE|SS_RIGHT|SS_CENTERIMAGE,10,70,tempClientRect.right-20,50,hWnd,(HMENU)53,NULL,NULL);//設(shè)置字體記大小SendMessage(GetDlgItem(hWnd,51),WM_SETFONT,(WPARAM)hCtlFont,1);SendMessage(GetDlgItem(hWnd,52),WM_SETFONT,(WPARAM)hCtlFont,1);SendMessage(GetDlgItem(hWnd,53),WM_SETFONT,(WPARAM)hCtlFont,1);}
4.3.1、計(jì)算器消息處理邏輯
在這里,我們?cè)O(shè)計(jì)鼠標(biāo)操作和鍵盤(pán)同時(shí)可以操作計(jì)算器,因此我們需要統(tǒng)一兩種操作的模式。我們將WM_CHAR、WM_KEYDOWN和WM_COMMAND的消息統(tǒng)一轉(zhuǎn)換成Calculator類(lèi)的指令。
void Calculator::OnCommand(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{char szButtonTitle[1024]="";GetWindowText(GetDlgItem(hWnd,LOWORD(wParam)),szButtonTitle,1024);OnExcuteString(hWnd,szButtonTitle);}void Calculator::OnChar(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{//判斷是否響應(yīng)相應(yīng)的按鍵bool tagResponseStatus=false;//當(dāng)用戶(hù)按下數(shù)字鍵,包括小鍵盤(pán)的數(shù)字鍵if('0'<=LOWORD(wParam) && LOWORD(wParam)<='9'){tagResponseStatus=true;}//當(dāng)按下加、減、乘、除按鍵時(shí)響應(yīng)if(LOWORD(wParam)==43 || LOWORD(wParam)==45 || LOWORD(wParam)==42 || LOWORD(wParam)==47){tagResponseStatus=true;}//當(dāng)按下左括號(hào)、右括號(hào)、小數(shù)點(diǎn)鍵if(LOWORD(wParam)==40 || LOWORD(wParam)==41 || LOWORD(wParam)==46){tagResponseStatus=true;}//對(duì)設(shè)置的按鍵命令進(jìn)行響應(yīng)if(tagResponseStatus==true){char szCommand[1024]="";sprintf(szCommand,"%c",LOWORD(wParam));OnExcuteString(hWnd,szCommand);}//當(dāng)按下回車(chē)、ESC、等號(hào)、BACKSPACE鍵執(zhí)行相應(yīng)的指令if(LOWORD(wParam)==13){OnExcuteString(hWnd,"RETURN");}if(LOWORD(wParam)==27){OnExcuteString(hWnd,"ESC");}if(LOWORD(wParam)==61){OnExcuteString(hWnd,"=");}if(LOWORD(wParam)==8){OnExcuteString(hWnd,"DEL");}}void Calculator::OnKeyDown(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{//鍵盤(pán)按下DEL鍵執(zhí)行清空顯示控件的指令if(LOWORD(wParam)==46){OnExcuteString(hWnd,"C");}}//屏幕顯示內(nèi)容void Calculator::OnPaint(HWND hWnd,HDC hDC)
{//顯示背景顏色if(hBackGroundBitmap!=NULL){BITMAP BM;RECT tempClientRect;GetClientRect(hWnd,&tempClientRect);HDC hTemDC=::CreateCompatibleDC(hDC);SelectObject(hTemDC,hBackGroundBitmap);GetObject(hBackGroundBitmap,sizeof(BITMAP),&BM);BitBlt(hDC,0,0,tempClientRect.right,tempClientRect.bottom,hTemDC,(BM.bmWidth-tempClientRect.right)/2,(BM.bmHeight-tempClientRect.bottom)/2,SRCCOPY);DeleteDC(hTemDC);}//調(diào)試信息,防止內(nèi)存泄露if(!true){char szTemp[1024]="";sprintf(szTemp,"st_StackNodeNum:%d",st_StackNodeNum);TextOut(hDC,10,120,szTemp,strlen(szTemp));}}
4.3.2、用戶(hù)自定義輸入表達(dá)式邏輯
在計(jì)算器字符表達(dá)式的輸入過(guò)程中,我們需要用戶(hù)根據(jù)一定的規(guī)則去輸入中綴表達(dá)式,而不能任意輸入錯(cuò)誤的表達(dá)式,我們會(huì)在用戶(hù)輸入時(shí)加上一些必要的校驗(yàn),比如不能連續(xù)出現(xiàn)兩個(gè)運(yùn)算符號(hào),括號(hào)需要成對(duì)出現(xiàn)等等。
void Calculator::OnExcuteString(HWND hWnd,char *szCommand)
{//每次輸入時(shí)重置錯(cuò)誤信息tagError=false;strcpy(szErrorMessage,"");//如果當(dāng)前需要添加的是數(shù)字if(IsNumber(szCommand)==true){if(strlen(szExpression)>0 && strlen(szExpression)<500){//獲取表達(dá)式的最后一個(gè)字符并轉(zhuǎn)換為字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//如果末尾不為右括號(hào)則直接添加if(strcmp(szEndChar,")")!=0){strcat(szExpression,szCommand);}}else{strcat(szExpression,szCommand);}}//如果當(dāng)前需要添加的是運(yùn)算符if(IsOperator(szCommand)==true){if(strlen(szExpression)>0 && strlen(szExpression)<500){//獲取表達(dá)式的最后一個(gè)字符并轉(zhuǎn)換為字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//判斷字符串結(jié)尾字符是否為數(shù)字if(IsNumber(szEndChar)==true || strcmp(szEndChar,")")==0 || strcmp(szEndChar,".")==0){strcat(szExpression,szCommand);}//如果末尾字符為運(yùn)算符,刪除用新的運(yùn)算符替換舊的運(yùn)算符else if(IsOperator(szEndChar)==true){//在新的字符串中進(jìn)行操作char szNewExpression[1024]="";//拷貝到新的字符串進(jìn)行操作strcpy(szNewExpression,szExpression);//刪除一個(gè)字符szNewExpression[strlen(szNewExpression)-1]='\0';//添加新的運(yùn)算符號(hào)strcat(szNewExpression,szCommand);//拷貝新的字符串到原算式表達(dá)式字符串strcpy(szExpression,szNewExpression);}}}//左括號(hào)的輸入if(strcmp(szCommand,"(")==0){if(strlen(szExpression)<500){//獲取表達(dá)式的最后一個(gè)字符并轉(zhuǎn)換為字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//末尾字符為運(yùn)算數(shù)字運(yùn)算符的替換,為數(shù)字(或其他)的則直接添加if(strlen(szExpression)==0 || IsOperator(szEndChar)==true || strcmp(szEndChar,"(")==0){strcat(szExpression,szCommand);}else{MessageBeep(MB_OK);}}}//右括號(hào)的輸入if(strcmp(szCommand,")")==0){if(strlen(szExpression)<500){//獲取表達(dá)式的最后一個(gè)字符并轉(zhuǎn)換為字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//判斷字符串結(jié)尾字符是否為數(shù)字if((IsNumber(szEndChar)==true || strcmp(szEndChar,")")==0) && GetCharAmount(szExpression,'(')>GetCharAmount(szExpression,')')){strcat(szExpression,szCommand);} else{MessageBeep(MB_OK);} }}//如果前一個(gè)字符是數(shù)字,則直接添加到算式中if(strcmp(szCommand,".")==0){if(strlen(szExpression)<500){//獲取表達(dá)式的最后一個(gè)字符并轉(zhuǎn)換為字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//判斷字符串結(jié)尾字符是否為數(shù)字if(IsNumber(szEndChar)==true){strcat(szExpression,szCommand);}}}//如果是數(shù)字,則直接添加到算式顯示控件if(strcmp(szCommand,"C")==0){strcpy(szExpression,"");strcpy(szCheckedExpression,"");strcpy(szResult,"");}//如果是數(shù)字,則直接添加到算式顯示控件if(strcmp(szCommand,"DEL")==0){if(strlen(szExpression)>0){//獲取表達(dá)式的最后一個(gè)字符并轉(zhuǎn)換為字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//在新的字符串中進(jìn)行操作char szNewExpression[1024]="";//拷貝到新的字符串進(jìn)行操作strcpy(szNewExpression,szExpression);//刪除一個(gè)字符szNewExpression[strlen(szNewExpression)-1]='\0';//拷貝新的字符串到原算式表達(dá)式字符串strcpy(szExpression,szNewExpression);}else{MessageBeep(MB_OK);}}//根據(jù)用戶(hù)輸入的字符串表達(dá)式智能糾錯(cuò)后計(jì)算結(jié)果GetResultValueByString();//當(dāng)按下等于號(hào)對(duì)結(jié)果進(jìn)行交換保存if(strcmp(szCommand,"=")==0){if(tagError!=true){//將結(jié)果保存到算式表達(dá)式字符串,并重置其他字符串strcpy(szExpression,szResult);strcpy(szCheckedExpression,"");strcpy(szResult,"");}else{MessageBeep(MB_OK);}}//更新顯示“ 字符串顯示框”SetWindowText(GetDlgItem(hWnd,51),szExpression);//更新顯示校驗(yàn)后的“ 字符串顯示框”SetWindowText(GetDlgItem(hWnd,52),szCheckedExpression);//更新顯示“ 字符串顯示框”SetWindowText(GetDlgItem(hWnd,53),szResult);//更新界面//InvalidateRect(hWnd,NULL,false);}
4.3.3、逆波蘭計(jì)算數(shù)學(xué)表達(dá)式
采用將用戶(hù)自定義輸入的中綴表達(dá)式字符串,通過(guò)棧的方式轉(zhuǎn)換為后綴表達(dá)式的算法,即逆波蘭方法及時(shí)并返回結(jié)果。
void Calculator::CalValue(Stack<double> *&ptNumStack,Stack<char> *&ptOperatorStack)
{double NumberLeft, NumberRight, NumberResult;char Operator;//將棧頂?shù)膬蓚€(gè)數(shù)字和一個(gè)操作符進(jìn)行出棧操作PopStack(ptNumStack, NumberRight);PopStack(ptNumStack, NumberLeft);PopStack(ptOperatorStack, Operator);//記錄除數(shù)為零的情況if(NumberRight==0 || NumberLeft==0){tagError=true;strcpy(szErrorMessage,"錯(cuò)誤:除數(shù)不能為零");}//對(duì)出棧的兩個(gè)數(shù)字和一個(gè)操作符進(jìn)行計(jì)算if (Operator == '+')NumberResult = NumberLeft + NumberRight;if (Operator == '-')NumberResult = NumberLeft - NumberRight;if (Operator == '*')NumberResult = NumberLeft * NumberRight;if (Operator == '/')NumberResult = NumberLeft / NumberRight;//將計(jì)算后的結(jié)果繼續(xù)押入棧中PushStack(ptNumStack, NumberResult);}//逆波蘭算法實(shí)現(xiàn)double Calculator::Polish(char *String, int len)
{Stack<double> *ptNumStack;Stack<char> *ptOperatorStack;//初始棧,最主要產(chǎn)生一個(gè)默認(rèn)的節(jié)點(diǎn)InitStack(ptNumStack);InitStack(ptOperatorStack);//逐字符讀取字符串的游標(biāo)位置int index = 0;//逐字符讀取字符串while(!IsStackEmpty(ptOperatorStack) || index<len){//當(dāng)前游標(biāo)位置小于字符串長(zhǎng)度時(shí),逐個(gè)獲取數(shù)字和運(yùn)算符號(hào)并進(jìn)行運(yùn)算;否則做收尾工作if(index<len){//如果當(dāng)前游標(biāo)位置為數(shù)字,則說(shuō)明這里是我們需要讀取數(shù)字的開(kāi)始位置if((String[index] >= '0' && String[index] <= '9') || String[index]=='.'){//將此后的數(shù)字區(qū)域讀取到臨時(shí)字符串char szTempNum[100]="";int iPos=0;//循環(huán)取得數(shù)字,當(dāng)遇到第一個(gè)不是數(shù)字或小數(shù)點(diǎn)的字符for(int i=index;i<len;i++){if((String[i] >= '0' && String[i] <= '9') || String[i]=='.'){szTempNum[iPos++]=String[i];index++;}else{break;}}szTempNum[iPos]='\0';//獲取到我們需要的數(shù)字,并將數(shù)字保存到棧中double tempNumber=atof(szTempNum);PushStack(ptNumStack, tempNumber);}else {//如果當(dāng)前字符串為運(yùn)算符,則根據(jù)運(yùn)行符的種類(lèi)進(jìn)行判斷if (String[index] == '(' || (GetStackTopValue(ptOperatorStack) == '(' && String[index] != ')') || IsStackEmpty(ptOperatorStack)){//遇到以上情況,則直接將運(yùn)行符保存的符號(hào)棧中PushStack(ptOperatorStack, String[index++]);}else if (String[index] == '+' || String[index] == '-'){//如果遇到加、減號(hào),就把此前已入棧的算式進(jìn)行計(jì)算,并將結(jié)果結(jié)果和符號(hào)重新入棧while (GetStackTopValue(ptOperatorStack) != '(' && !IsStackEmpty(ptOperatorStack)){CalValue(ptNumStack, ptOperatorStack);}PushStack(ptOperatorStack, String[index++]);}else if (String[index] == '*' || String[index] == '/') {//如果遇到乘、除號(hào),則把之前所有乘、除相關(guān)的算式進(jìn)行計(jì)算,并將結(jié)果結(jié)果和符號(hào)重新入棧while (GetStackTopValue(ptOperatorStack) == '*' || GetStackTopValue(ptOperatorStack) == '/'){CalValue(ptNumStack, ptOperatorStack);}PushStack(ptOperatorStack, String[index++]);}else if (String[index] == ')'){//當(dāng)遇到有括號(hào),則把所有括號(hào)對(duì)內(nèi)的算式計(jì)算完畢,右括號(hào)無(wú)需入棧while(GetStackTopValue(ptOperatorStack) != '(') {//當(dāng)棧為空時(shí)無(wú)需進(jìn)行計(jì)算跳出循環(huán)if(IsStackEmpty(ptOperatorStack))break;CalValue(ptNumStack, ptOperatorStack);}//當(dāng)計(jì)算完所有括號(hào)內(nèi)容的算式,彈出對(duì)應(yīng)的左括號(hào)char tempBracket;PopStack(ptOperatorStack, tempBracket);index++;}}}else{//遍歷完字符串所有字符后,只需對(duì)還未空的運(yùn)算符棧進(jìn)行逐步計(jì)算CalValue(ptNumStack, ptOperatorStack);}}//最后棧頂(根節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn))的數(shù)據(jù)就是表達(dá)式的結(jié)果double NumberValue=0;PopStack(ptNumStack,NumberValue);//在刪除所有子節(jié)點(diǎn)后銷(xiāo)毀棧的根節(jié)點(diǎn)DestroyStack(ptNumStack);DestroyStack(ptOperatorStack);//返回計(jì)算的結(jié)果return NumberValue;}
4.3.3、更新及顯示結(jié)果
前期已經(jīng)通過(guò)鍵盤(pán)或鼠標(biāo)消息處理,在szExpression中輸入了用戶(hù)自定義算式表達(dá)式,因此,我們直接在這里進(jìn)行計(jì)算,并將最終的結(jié)果反饋到szResult字符串。并通過(guò)類(lèi)的流程控制在顯示控件中顯示出來(lái)。
//根據(jù)字符串計(jì)算結(jié)果double Calculator::GetResultValueByString()
{//校驗(yàn)用戶(hù)的輸入,進(jìn)行自動(dòng)糾錯(cuò)處理strcpy(szCheckedExpression,szExpression);//對(duì)客戶(hù)輸入的算式進(jìn)行自動(dòng)糾錯(cuò)處理if(strlen(szCheckedExpression)!=0){//自動(dòng)去除末尾的非數(shù)字符號(hào)for(int i=strlen(szCheckedExpression)-1;i>=0;i--){//獲取表達(dá)式的最后一個(gè)字符并轉(zhuǎn)換為字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szCheckedExpression[strlen(szCheckedExpression)-1]);//智能刪除用戶(hù)算式表達(dá)式的末尾運(yùn)算符號(hào)和左括號(hào)if(IsNumber(szEndChar)==false && strcmp(szEndChar,")")!=0 && strcmp(szEndChar,".")!=0){szCheckedExpression[i]='\0';}else{break;}}//自動(dòng)添加用戶(hù)應(yīng)加未加的右括號(hào)int iTempBracketNum=GetCharAmount(szCheckedExpression,'(')-GetCharAmount(szCheckedExpression,')');//自動(dòng)添加用戶(hù)應(yīng)加未加的右括號(hào)for(int j=0;j<iTempBracketNum;j++){strcat(szCheckedExpression,")");}//將糾錯(cuò)后的字符串進(jìn)行計(jì)算處理if(strlen(szCheckedExpression)!=0){//根據(jù)糾錯(cuò)后的算式字符串計(jì)算結(jié)果ResultDate=Polish(szCheckedExpression,strlen(szCheckedExpression));//顯示出結(jié)果sprintf(szResult,"%0.10f",ResultDate);//刪除小數(shù)部分末尾的零TrimBackZero(szResult);//如果出現(xiàn)錯(cuò)誤,則只顯示錯(cuò)誤信息if(tagError==true){strcpy(szResult,szErrorMessage);}}}else{//用戶(hù)自定義字符串為空時(shí),重置結(jié)果字符串strcpy(szCheckedExpression,"");strcpy(szResult,"");}return 0;}
4.4、主窗口函數(shù)及消息循環(huán)
負(fù)責(zé)程序主窗口的創(chuàng)建,以及消息函數(shù)的集中分發(fā)處理。這里由于存在子按鍵控件,因此鼠標(biāo)點(diǎn)擊按鍵后,主窗口將無(wú)法收到鍵盤(pán)消息WM_CHAR和WM_KEYDIWN消息,導(dǎo)致鍵盤(pán)輸入失效,我們這里采用在主消息循環(huán)中,復(fù)制子窗口WM_CHAR和WM_KEYDIWN消息并手動(dòng)轉(zhuǎn)發(fā)給游戲主窗口的方法予以解決。同時(shí)在使用鍵盤(pán)過(guò)程中要注意,在中文輸入法時(shí)鍵盤(pán)輸入受到一定影響,需要手動(dòng)切換輸入法。另外,小鍵盤(pán)鎖也會(huì)影響到小鍵盤(pán)的輸入。
//消息處理模塊LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{HDC hDC=NULL;switch(message){case WM_CREATE://初始化并創(chuàng)建按鍵、算式顯示框和結(jié)果顯示框Calculators.Initialize(hWnd);return 0;case WM_PAINT:PAINTSTRUCT PS; hDC=BeginPaint(hWnd,&PS);//顯示屏幕內(nèi)容Calculators.OnPaint(hWnd,hDC);ReleaseDC(hWnd,hDC);return 0;case WM_COMMAND://根據(jù)消息執(zhí)行計(jì)算器的操作Calculators.OnCommand(hWnd,message,wParam,lParam);return 0;case WM_CHAR://根據(jù)消息執(zhí)行計(jì)算器的操作Calculators.OnChar(hWnd,message,wParam,lParam);return 0;case WM_KEYDOWN://根據(jù)消息執(zhí)行計(jì)算器的操作Calculators.OnKeyDown(hWnd,message,wParam,lParam);return 0;case WM_DESTROY:PostQuitMessage(0);return 0;}return DefWindowProc(hWnd,message,wParam,lParam);}//
//Calculator計(jì)算器經(jīng)典版
//作者:zhooyu
//2024.12.7
//CSDN主頁(yè)地址:https://blog.csdn.net/zhooyu
//CSDN文章地址:https://blog.csdn.net/zhooyu/article/details/144202897
////主函數(shù)int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR szCmdLine,int iCmdShow)
{MSG msg;HWND hWnd;CHAR szAppName[]="Calculator";//設(shè)置程序的樣式WNDCLASS WC;WC.style = CS_HREDRAW|CS_VREDRAW;WC.lpfnWndProc = WndProc;WC.cbClsExtra = 0;WC.cbWndExtra = 0;WC.hInstance = hInstance;WC.hIcon = LoadIcon(hInstance,IDI_APPLICATION);WC.hCursor = LoadCursor(hInstance,IDC_ARROW);WC.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);WC.lpszMenuName = NULL;WC.lpszClassName = szAppName;if(!RegisterClass(&WC)){return 0;}//創(chuàng)建窗口hWnd=CreateWindow(szAppName,szAppName,WS_OVERLAPPEDWINDOW&~WS_THICKFRAME&~WS_MAXIMIZEBOX,CW_USEDEFAULT,CW_USEDEFAULT,295,390,NULL,NULL,hInstance,NULL);//顯示更新窗口ShowWindow(hWnd,iCmdShow);UpdateWindow(hWnd);//消息循環(huán)while(GetMessage(&msg,NULL,0,0)){TranslateMessage(&msg);DispatchMessage(&msg);//調(diào)試信息if(msg.message==WM_CHAR || msg.message==WM_KEYDOWN){//調(diào)試信息if(!true){char szTemp[1024]="";sprintf(szTemp,"%d,%d",msg.hwnd,hWnd);MessageBox(NULL,szTemp,"",MB_OK);}//確保父窗口收到按鍵消息if(msg.hwnd!=hWnd){SendMessage(hWnd,msg.message,msg.wParam,msg.lParam);}}}return msg.wParam;}
5、完整源碼
該項(xiàng)目代碼僅僅包括一個(gè)cpp源文件,新建項(xiàng)目直接運(yùn)行,無(wú)需在資源編輯器中創(chuàng)建按鍵、顯示框等控件資源??梢栽赩S2010和VC6.0中新建項(xiàng)目后直接運(yùn)行。這里將全部源代碼整理如下,供大家參考。整理代碼不易,請(qǐng)大家不吝點(diǎn)贊關(guān)注,如果能留言就再好不過(guò)了,您的支持是我繼續(xù)前進(jìn)的動(dòng)力!!謝謝了先。
//加載系統(tǒng)頭文件#include "windows.h"#include "stdio.h"#include "math.h"//節(jié)點(diǎn)統(tǒng)計(jì)數(shù)字int st_StackNodeNum=0;//鏈棧template<typename Type>struct Stack
{Type num;Stack<Type>* ptNext;};//初始化棧template<typename Type>void InitStack(Stack<Type>*& Node)
{Node = (Stack<Type>*)malloc(sizeof(Stack<Type>));Node->ptNext = NULL;st_StackNodeNum++;}//頭插法入棧template<typename Type>void PushStack(Stack<Type>*& Node, Type value)
{Stack<Type>* pt = (Stack<Type>*)malloc(sizeof(Stack<Type>));pt->num = value;pt->ptNext = Node->ptNext;Node->ptNext = pt;st_StackNodeNum++;}//頭插法出棧template<typename Type>void PopStack(Stack<Type>*& Node, Type& value)
{Stack<Type>* pt = Node->ptNext;value = pt->num;Node->ptNext = pt->ptNext;delete pt;st_StackNodeNum--;}//頭插法出棧template<typename Type>void DestroyStack(Stack<Type>*& Node)
{if(Node->ptNext == NULL){delete Node;Node=NULL;st_StackNodeNum--;}}//判斷棧是否為空,除去沒(méi)有存數(shù)據(jù)的首個(gè)節(jié)點(diǎn)外template<typename Type>bool IsStackEmpty(Stack<Type>* Node)
{return Node->ptNext == NULL;}//獲取棧頂部節(jié)點(diǎn)的數(shù)據(jù)template<typename Type>Type GetStackTopValue(Stack<Type>* Node)
{if(Node->ptNext !=NULL){return Node->ptNext->num;}else{return 0;}}//省略掉數(shù)字的小數(shù)點(diǎn)后末尾多余的零void TrimBackZero(char *szString)
{//標(biāo)記小數(shù)點(diǎn)的位置int iDotPos=-1;//先找到小數(shù)點(diǎn)的位置for(int i=0;i<lstrlen(szString);i++){if(szString[i]=='.'){iDotPos=i;break;}}//尋找末尾多余的零for(int j=lstrlen(szString)-1;j>=iDotPos;j--){if(szString[j]=='.' || szString[j]=='0'){szString[j]='\0';}else{break;}}}//獲取字符串中某個(gè)字符的個(gè)數(shù)int GetCharAmount(char *szString,char sign)
{int iAmount=0;for(int i=0;i<lstrlen(szString);i++){if(szString[i]==sign)iAmount++;}return iAmount;}//判斷是否為數(shù)字bool IsNumber(char *szString)
{if(strcmp(szString,"0")==0)return true;if(strcmp(szString,"1")==0)return true;if(strcmp(szString,"2")==0)return true;if(strcmp(szString,"3")==0)return true;if(strcmp(szString,"4")==0)return true;if(strcmp(szString,"5")==0)return true;if(strcmp(szString,"6")==0)return true;if(strcmp(szString,"7")==0)return true;if(strcmp(szString,"8")==0)return true;if(strcmp(szString,"9")==0)return true;return false;}//判斷是否為運(yùn)算符號(hào)bool IsOperator(char *szString)
{if(strcmp(szString,"+")==0)return true;if(strcmp(szString,"-")==0)return true;if(strcmp(szString,"*")==0)return true;if(strcmp(szString,"/")==0)return true;return false;}//按鍵最大數(shù)量#define BUTTONMAXNUM 20//計(jì)算器類(lèi)class Calculator
{public://用于保存算式表達(dá)式字符串char szExpression[1024];//用于保存經(jīng)過(guò)校驗(yàn)的算式表達(dá)式字符串char szCheckedExpression[1024];//用于保存計(jì)算結(jié)果的字符串char szResult[1024];//控件字體設(shè)置HFONT hCtlFont;//用于存儲(chǔ)雙精度格式的結(jié)果double ResultDate;//標(biāo)記是否出現(xiàn)錯(cuò)誤bool tagError;//記錄錯(cuò)誤信息char szErrorMessage[1024];//背景圖片HBITMAP hBackGroundBitmap;public:Calculator();~Calculator();//初始化,用于創(chuàng)建按鍵控件和顯示控件void Initialize(HWND hWnd);//相應(yīng)鍵盤(pán)輸入轉(zhuǎn)換成統(tǒng)一的指令(鼠標(biāo)點(diǎn)擊按鍵)void OnCommand(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);//相應(yīng)鍵盤(pán)輸入轉(zhuǎn)換成統(tǒng)一的指令(數(shù)字按鍵和運(yùn)算符號(hào)按鍵)void OnChar(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);//相應(yīng)鍵盤(pán)輸入轉(zhuǎn)換成統(tǒng)一的指令(其他特殊按鍵)void OnKeyDown(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);//根據(jù)用戶(hù)的輸入指令進(jìn)行相應(yīng)的處理void OnExcuteString(HWND hWnd,char *szCommand);//屏幕顯示內(nèi)容void OnPaint(HWND hWnd,HDC hDC);//根據(jù)字符串計(jì)算結(jié)果double GetResultValueByString();//計(jì)算分步結(jié)果void CalValue(Stack<double> *&ptNumStack,Stack<char> *&ptOperatorStack);//逆波蘭算法實(shí)現(xiàn)double Polish(char *String, int len);};//自定義計(jì)算器類(lèi)實(shí)例Calculator Calculators;Calculator::Calculator()
{hCtlFont=NULL;strcpy(szExpression,"");strcpy(szCheckedExpression,"");strcpy(szResult,"");ResultDate=0;tagError=false;strcpy(szErrorMessage,"");hBackGroundBitmap=NULL;hBackGroundBitmap=(HBITMAP)LoadImage(NULL,"bg.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);}Calculator::~Calculator()
{//刪除字體資源DeleteObject(hCtlFont);}void Calculator::Initialize(HWND hWnd)
{//控件字體設(shè)置HFONT hCtlFont=CreateFont(22,0,0,0,1000,0,0,0,0,0,0,PROOF_QUALITY,0,"宋體");//獲取窗口的大小RECT tempClientRect;GetClientRect(hWnd,&tempClientRect);//自定義按鍵的文字標(biāo)題char szButtonTitle[BUTTONMAXNUM][1024]={".","0","C","+","1","2","3","-","4","5","6","*","7","8","9","/","(",")","DEL","="};//創(chuàng)建按鍵控件,并設(shè)置按鍵的位置和標(biāo)題for(int i=0;i<BUTTONMAXNUM;i++){//設(shè)置按鍵的寬和高int w=60,h=35,gap=10;//設(shè)置按鍵的坐標(biāo)位置int x=10+(i%4)*(w+gap),y=tempClientRect.bottom-h-gap-(i/4)*(h+gap);//創(chuàng)建按鍵子控件CreateWindow("BUTTON",szButtonTitle[i],WS_CHILD|WS_VISIBLE|WS_CLIPCHILDREN|WS_CLIPCHILDREN|WS_CLIPSIBLINGS,x,y,60,35,hWnd,(HMENU)i,NULL,NULL);//設(shè)置字體記大小SendMessage(GetDlgItem(hWnd,i),WM_SETFONT,(WPARAM)hCtlFont,1);}//創(chuàng)建顯示子控件,算式顯示屏幕CreateWindowEx(WS_EX_CLIENTEDGE,"STATIC","",WS_CHILD|WS_VISIBLE|SS_RIGHT|SS_CENTERIMAGE,10,10,tempClientRect.right-20,50,hWnd,(HMENU)51,NULL,NULL);//創(chuàng)建顯示子控件,結(jié)果顯示屏幕CreateWindowEx(WS_EX_CLIENTEDGE,"STATIC","",WS_CHILD|WS_VISIBLE|SS_RIGHT|SS_CENTERIMAGE,10,70,tempClientRect.right-20,50,hWnd,(HMENU)53,NULL,NULL);//設(shè)置字體記大小SendMessage(GetDlgItem(hWnd,51),WM_SETFONT,(WPARAM)hCtlFont,1);SendMessage(GetDlgItem(hWnd,52),WM_SETFONT,(WPARAM)hCtlFont,1);SendMessage(GetDlgItem(hWnd,53),WM_SETFONT,(WPARAM)hCtlFont,1);}void Calculator::OnCommand(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{char szButtonTitle[1024]="";GetWindowText(GetDlgItem(hWnd,LOWORD(wParam)),szButtonTitle,1024);OnExcuteString(hWnd,szButtonTitle);}void Calculator::OnChar(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{//判斷是否響應(yīng)相應(yīng)的按鍵bool tagResponseStatus=false;//當(dāng)用戶(hù)按下數(shù)字鍵,包括小鍵盤(pán)的數(shù)字鍵if('0'<=LOWORD(wParam) && LOWORD(wParam)<='9'){tagResponseStatus=true;}//當(dāng)按下加、減、乘、除按鍵時(shí)響應(yīng)if(LOWORD(wParam)==43 || LOWORD(wParam)==45 || LOWORD(wParam)==42 || LOWORD(wParam)==47){tagResponseStatus=true;}//當(dāng)按下左括號(hào)、右括號(hào)、小數(shù)點(diǎn)鍵if(LOWORD(wParam)==40 || LOWORD(wParam)==41 || LOWORD(wParam)==46){tagResponseStatus=true;}//對(duì)設(shè)置的按鍵命令進(jìn)行響應(yīng)if(tagResponseStatus==true){char szCommand[1024]="";sprintf(szCommand,"%c",LOWORD(wParam));OnExcuteString(hWnd,szCommand);}//當(dāng)按下回車(chē)、ESC、等號(hào)、BACKSPACE鍵執(zhí)行相應(yīng)的指令if(LOWORD(wParam)==13){OnExcuteString(hWnd,"RETURN");}if(LOWORD(wParam)==27){OnExcuteString(hWnd,"ESC");}if(LOWORD(wParam)==61){OnExcuteString(hWnd,"=");}if(LOWORD(wParam)==8){OnExcuteString(hWnd,"DEL");}}void Calculator::OnKeyDown(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{//鍵盤(pán)按下DEL鍵執(zhí)行清空顯示控件的指令if(LOWORD(wParam)==46){OnExcuteString(hWnd,"C");}}//屏幕顯示內(nèi)容void Calculator::OnPaint(HWND hWnd,HDC hDC)
{//顯示背景顏色if(hBackGroundBitmap!=NULL){BITMAP BM;RECT tempClientRect;GetClientRect(hWnd,&tempClientRect);HDC hTemDC=::CreateCompatibleDC(hDC);SelectObject(hTemDC,hBackGroundBitmap);GetObject(hBackGroundBitmap,sizeof(BITMAP),&BM);BitBlt(hDC,0,0,tempClientRect.right,tempClientRect.bottom,hTemDC,(BM.bmWidth-tempClientRect.right)/2,(BM.bmHeight-tempClientRect.bottom)/2,SRCCOPY);DeleteDC(hTemDC);}//調(diào)試信息,防止內(nèi)存泄露if(!true){char szTemp[1024]="";sprintf(szTemp,"st_StackNodeNum:%d",st_StackNodeNum);TextOut(hDC,10,120,szTemp,strlen(szTemp));}}void Calculator::OnExcuteString(HWND hWnd,char *szCommand)
{//每次輸入時(shí)重置錯(cuò)誤信息tagError=false;strcpy(szErrorMessage,"");//如果當(dāng)前需要添加的是數(shù)字if(IsNumber(szCommand)==true){if(strlen(szExpression)>0 && strlen(szExpression)<500){//獲取表達(dá)式的最后一個(gè)字符并轉(zhuǎn)換為字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//如果末尾不為右括號(hào)則直接添加if(strcmp(szEndChar,")")!=0){strcat(szExpression,szCommand);}}else{strcat(szExpression,szCommand);}}//如果當(dāng)前需要添加的是運(yùn)算符if(IsOperator(szCommand)==true){if(strlen(szExpression)>0 && strlen(szExpression)<500){//獲取表達(dá)式的最后一個(gè)字符并轉(zhuǎn)換為字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//判斷字符串結(jié)尾字符是否為數(shù)字if(IsNumber(szEndChar)==true || strcmp(szEndChar,")")==0 || strcmp(szEndChar,".")==0){strcat(szExpression,szCommand);}//如果末尾字符為運(yùn)算符,刪除用新的運(yùn)算符替換舊的運(yùn)算符else if(IsOperator(szEndChar)==true){//在新的字符串中進(jìn)行操作char szNewExpression[1024]="";//拷貝到新的字符串進(jìn)行操作strcpy(szNewExpression,szExpression);//刪除一個(gè)字符szNewExpression[strlen(szNewExpression)-1]='\0';//添加新的運(yùn)算符號(hào)strcat(szNewExpression,szCommand);//拷貝新的字符串到原算式表達(dá)式字符串strcpy(szExpression,szNewExpression);}}}//左括號(hào)的輸入if(strcmp(szCommand,"(")==0){if(strlen(szExpression)<500){//獲取表達(dá)式的最后一個(gè)字符并轉(zhuǎn)換為字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//末尾字符為運(yùn)算數(shù)字運(yùn)算符的替換,為數(shù)字(或其他)的則直接添加if(strlen(szExpression)==0 || IsOperator(szEndChar)==true || strcmp(szEndChar,"(")==0){strcat(szExpression,szCommand);}else{MessageBeep(MB_OK);}}}//右括號(hào)的輸入if(strcmp(szCommand,")")==0){if(strlen(szExpression)<500){//獲取表達(dá)式的最后一個(gè)字符并轉(zhuǎn)換為字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//判斷字符串結(jié)尾字符是否為數(shù)字if((IsNumber(szEndChar)==true || strcmp(szEndChar,")")==0) && GetCharAmount(szExpression,'(')>GetCharAmount(szExpression,')')){strcat(szExpression,szCommand);} else{MessageBeep(MB_OK);} }}//如果前一個(gè)字符是數(shù)字,則直接添加到算式中if(strcmp(szCommand,".")==0){if(strlen(szExpression)<500){//獲取表達(dá)式的最后一個(gè)字符并轉(zhuǎn)換為字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//判斷字符串結(jié)尾字符是否為數(shù)字if(IsNumber(szEndChar)==true){strcat(szExpression,szCommand);}}}//如果是數(shù)字,則直接添加到算式顯示控件if(strcmp(szCommand,"C")==0){strcpy(szExpression,"");strcpy(szCheckedExpression,"");strcpy(szResult,"");}//如果是數(shù)字,則直接添加到算式顯示控件if(strcmp(szCommand,"DEL")==0){if(strlen(szExpression)>0){//獲取表達(dá)式的最后一個(gè)字符并轉(zhuǎn)換為字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//在新的字符串中進(jìn)行操作char szNewExpression[1024]="";//拷貝到新的字符串進(jìn)行操作strcpy(szNewExpression,szExpression);//刪除一個(gè)字符szNewExpression[strlen(szNewExpression)-1]='\0';//拷貝新的字符串到原算式表達(dá)式字符串strcpy(szExpression,szNewExpression);}else{MessageBeep(MB_OK);}}//根據(jù)用戶(hù)輸入的字符串表達(dá)式智能糾錯(cuò)后計(jì)算結(jié)果GetResultValueByString();//當(dāng)按下等于號(hào)對(duì)結(jié)果進(jìn)行交換保存if(strcmp(szCommand,"=")==0){if(tagError!=true){//將結(jié)果保存到算式表達(dá)式字符串,并重置其他字符串strcpy(szExpression,szResult);strcpy(szCheckedExpression,"");strcpy(szResult,"");}else{MessageBeep(MB_OK);}}//更新顯示“ 字符串顯示框”SetWindowText(GetDlgItem(hWnd,51),szExpression);//更新顯示校驗(yàn)后的“ 字符串顯示框”SetWindowText(GetDlgItem(hWnd,52),szCheckedExpression);//更新顯示“ 字符串顯示框”SetWindowText(GetDlgItem(hWnd,53),szResult);//更新界面//InvalidateRect(hWnd,NULL,false);}void Calculator::CalValue(Stack<double> *&ptNumStack,Stack<char> *&ptOperatorStack)
{double NumberLeft, NumberRight, NumberResult;char Operator;//將棧頂?shù)膬蓚€(gè)數(shù)字和一個(gè)操作符進(jìn)行出棧操作PopStack(ptNumStack, NumberRight);PopStack(ptNumStack, NumberLeft);PopStack(ptOperatorStack, Operator);//記錄除數(shù)為零的情況if(NumberRight==0 || NumberLeft==0){tagError=true;strcpy(szErrorMessage,"錯(cuò)誤:除數(shù)不能為零");}//對(duì)出棧的兩個(gè)數(shù)字和一個(gè)操作符進(jìn)行計(jì)算if (Operator == '+')NumberResult = NumberLeft + NumberRight;if (Operator == '-')NumberResult = NumberLeft - NumberRight;if (Operator == '*')NumberResult = NumberLeft * NumberRight;if (Operator == '/')NumberResult = NumberLeft / NumberRight;//將計(jì)算后的結(jié)果繼續(xù)押入棧中PushStack(ptNumStack, NumberResult);}//逆波蘭算法實(shí)現(xiàn)double Calculator::Polish(char *String, int len)
{Stack<double> *ptNumStack;Stack<char> *ptOperatorStack;//初始棧,最主要產(chǎn)生一個(gè)默認(rèn)的節(jié)點(diǎn)InitStack(ptNumStack);InitStack(ptOperatorStack);//逐字符讀取字符串的游標(biāo)位置int index = 0;//逐字符讀取字符串while(!IsStackEmpty(ptOperatorStack) || index<len){//當(dāng)前游標(biāo)位置小于字符串長(zhǎng)度時(shí),逐個(gè)獲取數(shù)字和運(yùn)算符號(hào)并進(jìn)行運(yùn)算;否則做收尾工作if(index<len){//如果當(dāng)前游標(biāo)位置為數(shù)字,則說(shuō)明這里是我們需要讀取數(shù)字的開(kāi)始位置if((String[index] >= '0' && String[index] <= '9') || String[index]=='.'){//將此后的數(shù)字區(qū)域讀取到臨時(shí)字符串char szTempNum[100]="";int iPos=0;//循環(huán)取得數(shù)字,當(dāng)遇到第一個(gè)不是數(shù)字或小數(shù)點(diǎn)的字符for(int i=index;i<len;i++){if((String[i] >= '0' && String[i] <= '9') || String[i]=='.'){szTempNum[iPos++]=String[i];index++;}else{break;}}szTempNum[iPos]='\0';//獲取到我們需要的數(shù)字,并將數(shù)字保存到棧中double tempNumber=atof(szTempNum);PushStack(ptNumStack, tempNumber);}else {//如果當(dāng)前字符串為運(yùn)算符,則根據(jù)運(yùn)行符的種類(lèi)進(jìn)行判斷if (String[index] == '(' || (GetStackTopValue(ptOperatorStack) == '(' && String[index] != ')') || IsStackEmpty(ptOperatorStack)){//遇到以上情況,則直接將運(yùn)行符保存的符號(hào)棧中PushStack(ptOperatorStack, String[index++]);}else if (String[index] == '+' || String[index] == '-'){//如果遇到加、減號(hào),就把此前已入棧的算式進(jìn)行計(jì)算,并將結(jié)果結(jié)果和符號(hào)重新入棧while (GetStackTopValue(ptOperatorStack) != '(' && !IsStackEmpty(ptOperatorStack)){CalValue(ptNumStack, ptOperatorStack);}PushStack(ptOperatorStack, String[index++]);}else if (String[index] == '*' || String[index] == '/') {//如果遇到乘、除號(hào),則把之前所有乘、除相關(guān)的算式進(jìn)行計(jì)算,并將結(jié)果結(jié)果和符號(hào)重新入棧while (GetStackTopValue(ptOperatorStack) == '*' || GetStackTopValue(ptOperatorStack) == '/'){CalValue(ptNumStack, ptOperatorStack);}PushStack(ptOperatorStack, String[index++]);}else if (String[index] == ')'){//當(dāng)遇到有括號(hào),則把所有括號(hào)對(duì)內(nèi)的算式計(jì)算完畢,右括號(hào)無(wú)需入棧while(GetStackTopValue(ptOperatorStack) != '(') {//當(dāng)棧為空時(shí)無(wú)需進(jìn)行計(jì)算跳出循環(huán)if(IsStackEmpty(ptOperatorStack))break;CalValue(ptNumStack, ptOperatorStack);}//當(dāng)計(jì)算完所有括號(hào)內(nèi)容的算式,彈出對(duì)應(yīng)的左括號(hào)char tempBracket;PopStack(ptOperatorStack, tempBracket);index++;}}}else{//遍歷完字符串所有字符后,只需對(duì)還未空的運(yùn)算符棧進(jìn)行逐步計(jì)算CalValue(ptNumStack, ptOperatorStack);}}//最后棧頂(根節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn))的數(shù)據(jù)就是表達(dá)式的結(jié)果double NumberValue=0;PopStack(ptNumStack,NumberValue);//在刪除所有子節(jié)點(diǎn)后銷(xiāo)毀棧的根節(jié)點(diǎn)DestroyStack(ptNumStack);DestroyStack(ptOperatorStack);//返回計(jì)算的結(jié)果return NumberValue;}//根據(jù)字符串計(jì)算結(jié)果double Calculator::GetResultValueByString()
{//校驗(yàn)用戶(hù)的輸入,進(jìn)行自動(dòng)糾錯(cuò)處理strcpy(szCheckedExpression,szExpression);//對(duì)客戶(hù)輸入的算式進(jìn)行自動(dòng)糾錯(cuò)處理if(strlen(szCheckedExpression)!=0){//自動(dòng)去除末尾的非數(shù)字符號(hào)for(int i=strlen(szCheckedExpression)-1;i>=0;i--){//獲取表達(dá)式的最后一個(gè)字符并轉(zhuǎn)換為字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szCheckedExpression[strlen(szCheckedExpression)-1]);//智能刪除用戶(hù)算式表達(dá)式的末尾運(yùn)算符號(hào)和左括號(hào)if(IsNumber(szEndChar)==false && strcmp(szEndChar,")")!=0 && strcmp(szEndChar,".")!=0){szCheckedExpression[i]='\0';}else{break;}}//自動(dòng)添加用戶(hù)應(yīng)加未加的右括號(hào)int iTempBracketNum=GetCharAmount(szCheckedExpression,'(')-GetCharAmount(szCheckedExpression,')');//自動(dòng)添加用戶(hù)應(yīng)加未加的右括號(hào)for(int j=0;j<iTempBracketNum;j++){strcat(szCheckedExpression,")");}//將糾錯(cuò)后的字符串進(jìn)行計(jì)算處理if(strlen(szCheckedExpression)!=0){//根據(jù)糾錯(cuò)后的算式字符串計(jì)算結(jié)果ResultDate=Polish(szCheckedExpression,strlen(szCheckedExpression));//顯示出結(jié)果sprintf(szResult,"%0.10f",ResultDate);//刪除小數(shù)部分末尾的零TrimBackZero(szResult);//如果出現(xiàn)錯(cuò)誤,則只顯示錯(cuò)誤信息if(tagError==true){strcpy(szResult,szErrorMessage);}}}else{//用戶(hù)自定義字符串為空時(shí),重置結(jié)果字符串strcpy(szCheckedExpression,"");strcpy(szResult,"");}return 0;}//消息處理模塊LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{HDC hDC=NULL;switch(message){case WM_CREATE://初始化并創(chuàng)建按鍵、算式顯示框和結(jié)果顯示框Calculators.Initialize(hWnd);return 0;case WM_PAINT:PAINTSTRUCT PS; hDC=BeginPaint(hWnd,&PS);//顯示屏幕內(nèi)容Calculators.OnPaint(hWnd,hDC);ReleaseDC(hWnd,hDC);return 0;case WM_COMMAND://根據(jù)消息執(zhí)行計(jì)算器的操作Calculators.OnCommand(hWnd,message,wParam,lParam);return 0;case WM_CHAR://根據(jù)消息執(zhí)行計(jì)算器的操作Calculators.OnChar(hWnd,message,wParam,lParam);return 0;case WM_KEYDOWN://根據(jù)消息執(zhí)行計(jì)算器的操作Calculators.OnKeyDown(hWnd,message,wParam,lParam);return 0;case WM_DESTROY:PostQuitMessage(0);return 0;}return DefWindowProc(hWnd,message,wParam,lParam);}//
//Calculator計(jì)算器經(jīng)典版
//作者:zhooyu
//2024.12.7
//CSDN主頁(yè)地址:https://blog.csdn.net/zhooyu
//CSDN文章地址:https://blog.csdn.net/zhooyu/article/details/144202897
////主函數(shù)int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR szCmdLine,int iCmdShow)
{MSG msg;HWND hWnd;CHAR szAppName[]="Calculator";//設(shè)置程序的樣式WNDCLASS WC;WC.style = CS_HREDRAW|CS_VREDRAW;WC.lpfnWndProc = WndProc;WC.cbClsExtra = 0;WC.cbWndExtra = 0;WC.hInstance = hInstance;WC.hIcon = LoadIcon(hInstance,IDI_APPLICATION);WC.hCursor = LoadCursor(hInstance,IDC_ARROW);WC.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);WC.lpszMenuName = NULL;WC.lpszClassName = szAppName;if(!RegisterClass(&WC)){return 0;}//創(chuàng)建窗口hWnd=CreateWindow(szAppName,szAppName,WS_OVERLAPPEDWINDOW&~WS_THICKFRAME&~WS_MAXIMIZEBOX,CW_USEDEFAULT,CW_USEDEFAULT,295,390,NULL,NULL,hInstance,NULL);//顯示更新窗口ShowWindow(hWnd,iCmdShow);UpdateWindow(hWnd);//消息循環(huán)while(GetMessage(&msg,NULL,0,0)){TranslateMessage(&msg);DispatchMessage(&msg);//調(diào)試信息if(msg.message==WM_CHAR || msg.message==WM_KEYDOWN){//調(diào)試信息if(!true){char szTemp[1024]="";sprintf(szTemp,"%d,%d",msg.hwnd,hWnd);MessageBox(NULL,szTemp,"",MB_OK);}//確保父窗口收到按鍵消息if(msg.hwnd!=hWnd){SendMessage(hWnd,msg.message,msg.wParam,msg.lParam);}}}return msg.wParam;}
6、源碼下載
該源碼可以在VS2010和VC6.0中無(wú)差異運(yùn)行,因此就上傳了兩個(gè)版本的源碼,方便運(yùn)行。
6.1、VS2010源碼下載
CSDN下載地址:Calculator20241207-15-vs2010.rar
6.2、VC6.0源碼下載
CSDN下載地址:Calculator20241207-15-vc6.0.rar