C++实现一个经典计算器(逆波兰算法)附源码
1、本篇要实现的内容
最近,大家讨论计算器的实现比较热,今天我也来用C++和Visual Studio实现一个计算器的小程序。这里使用逆波兰算法,能够根据当前用户输入的算式表达式字符串,计算出所要的结果,算式字符串可以包括加、减、乘、除和括号,支持整数、小数,鼠标和键盘均可操作,实现了一个较为经典的计算器功能。后期如果有时间我们再实现一些更多的计算器功能。本篇实现的效果如下:
2、设计目标
我们今天想制作一个计算器,需要基本上能达到日常使用的需求。首先它得有可操作的图形窗口界面,它要能够满足我们一些基本的计算需求,如整数和小数的加、减、乘、除,顺便再把括号功能附加上。同时我们在设计的时候,还允许用户输入算式表达式字符串,程序能根据用户输入算式表达式字符串,经过一些智能纠错后,对纠错后的算式表达式进行实时计算,并最终显示出结果。
2.1 、运行环境
操作系统:Windows10操作系统
编译环境:Microsoft Visual Studio 2010(VC6.0也可以直接编译运行)
其它事项:源代码仅仅包括一个cpp源文件,新建项目可直接编译运行,无需在资源编辑器中额外创建按键、显示框等控件资源。
2.2、实现图形化界面
首先计算器要方便使用,我们必须为它创建一个友好的图形界面。我们首先为他创建一个应用窗口,并为窗口添加相应的控件。控间最主要包括两大部分,一部分是用于用户输入的响应按键,另一部分是用于反馈用户输入和计算结果的显示控件。为了简化项目,我们这里采用系统CreateWindow函数创建的按键BUTTON控件和STATIC控件,分别来响应用户输入和输出。图形的区域分布如下:
2.3、实现字符串算式自动识别计算
通过字符串算式自动识别数学表达式有两个优点。第一个优点,可以方便用户随时校对自己输入算式表达式的正确性。在计算时我不仅仅需要看到的是计算后的得数,有时候我还需要看到我们已经输入的算术表达式,方便我校对输入的式子是否正确,如发现错误还可以及时修改。第二个优点,字符串算式表达式可以考虑到算式计算的优先级。在普通没有字符串表达式的计算器中,我们每输入一个算术符号和数字,就必须要计算出这一步的结果。如此循环操作,再往下继续输入运算符号和数字,屏幕只显示当前的结果。那么这样就势必无法考虑到加减乘除运算规则,只能根据用户输入算式的先后顺序计算,更没有办法考虑到括号的优先运算。那么字符串算数表达式就可以完美解决这个问题,这里还要用到逆波兰算法。
在这个算式中我们需要先计算3*13=39的乘法,在计算12+39=51的加法。
2.4、支持加、减、乘、除和括号
由于使用了逆波兰算法,这里运算我们支持加减乘除,还添加了对括号的支持。我们采用了字符串算式格式,我们可以方便的对加减乘除和括号的运算规则进行支持。因为在日常的运算中,如果拿着计算器还需要自己去考虑一个算式的运算顺序的话,会是一个很糟糕的体验。
2.5、实时更新运算结果
我们在我们在制作计算机前期构想的时候,借鉴了手机上自带计算器功能的一些创意,用户每输入一个字符都会更新并影响到最终结果。在使用计算器的时候,当用户每输入一个数字或者符号时,计算器都会根据当前已经输入的算式表达式,进行智能分析,预估出用户可能需要的结果,随即实时计算出结果并显示。
2.6、智能运算符号校验
我们是采用对字符串进行逆波兰法计算,并且是实时(每输入一个数字或字符都会影响到结果)计算,因此对算式字符串的规范性检测要求较高。但是我们日常在输入字符串表达式的时候,难免会存在一些手误,比如说连续输入两个乘号等等,那么这类的错误操作就需要我们用用户输入逻辑去加以规范或限制。同时还有用户在输入括号时,表达式中的左右括号数量不一致等问题,将会导致计算出现错误。我们这里通过输入逻辑检测解决了用户输入表达式的规范性。
2.7、错误判断提示
在遇到除数为零的特殊情况时,我们需要在结果中输出错误提示,否则计算会出现意外。如下图:
2.8、支持整数、小数运算
这里我们要双精度数据类型进行计算,确保计算的准确性。对小数的计算是我们日常生活中不可少的,部分计算器并没有增加对小数的支持。本次在程序设计的开始,就考虑到了这一点。这里包括对有限小数的计算,包括对循环小数的计算,以及无限循环小数结果的显示逻辑。
2.9、使用逆波兰算法计算数学表达式
一. 波兰式(前缀表达式)
波兰逻辑学家J.Lukasiewicz于1929年提出的表示表达式的一种方式,即二元运算符至于运算数之前的一种表达方式。
二.中缀表达式
普通的表示表达式的一种方法,将二元运算符置于运算数中间,也是大多数情况下使用的一种方法。
三.逆波兰式(后缀表达式)
与波兰式相反,是二元运算符置于运算数之后的一种表达方式。每一运算符都置于其运算对象之后,故称为后缀表示。
三种表达式的形象实例如下:
逆波兰式的应用——算术表达式求值
逆波兰式,也称逆波兰记法(Reverse Polish Notation)。在数据结构中,使用栈的概念完成表达式的求值操作,在计算机系统处理表达式的计算过程中,将中缀表达式转换为后缀表达式的形式进行解析转换并实施计算,这就是逆波兰算法的应用。
具体实现方法大致为:
- 设两个栈,操作数栈和运算符栈;
- 操作数依次入操作数栈;
- 运算符入栈前与运算符的栈顶运算符比较优先级;
- 优先级高于栈顶运算符,压入栈,读入下一个符号;
- 优先级低于栈顶运算符,栈顶运算符出栈,操作数栈退出两个操作数,进行运算,结果压入操作数栈;
- 优先级相等,左右括号相遇,栈顶运算符出栈即可;
- 后缀表达式读完,栈顶为运算结果。
2.10、支持背景图片
程序设计了一个简单的游戏背景设定,程序当前文件夹中放置名为bg.bmp的图片文件后,程序会自动加载并居中显示背景图片,大家可以放上自己喜欢的背景图片。
3、源码下载
该源码可以在VS2010和VC6.0中无差异运行,因此就上传了两个版本的源码,方便运行。
3.1、VS2010源码下载
CSDN下载地址:Calculator20241207-15-vs2010.rar
3.2、VC6.0源码下载
CSDN下载地址:Calculator20241207-15-vc6.0.rar
4、源代码实现过程
我们根据实现功能的不同,可以大致将整个项目分为以下各个模块。
4.1、链表栈的实现
由于逆波兰法会要用到栈操作,我们预先定义一个链栈,在字符串表达式计算过程中会频繁出栈和进栈,已经栈的初始化和销毁,要注意内存泄露。
//加载系统头文件#include "windows.h"#include "stdio.h"#include "math.h"//节点统计数字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--;}}//判断栈是否为空,除去没有存数据的首个节点外template<typename Type>bool IsStackEmpty(Stack<Type>* Node)
{return Node->ptNext == NULL;}//获取栈顶部节点的数据template<typename Type>Type GetStackTopValue(Stack<Type>* Node)
{if(Node->ptNext !=NULL){return Node->ptNext->num;}else{return 0;}}
4.2、字符串操作函数
在字符表达式的输入和处理过程中,会遇到一些必须的字符处理函数,我们在这里定义。
//省略掉数字的小数点后末尾多余的零void TrimBackZero(char *szString)
{//标记小数点的位置int iDotPos=-1;//先找到小数点的位置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;}}}//获取字符串中某个字符的个数int GetCharAmount(char *szString,char sign)
{int iAmount=0;for(int i=0;i<lstrlen(szString);i++){if(szString[i]==sign)iAmount++;}return iAmount;}//判断是否为数字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;}//判断是否为运算符号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、计算器类
为了实现计算器的各个功能,我们集成到一个计算器类中进行操作。
//按键最大数量#define BUTTONMAXNUM 20//计算器类class Calculator
{public://用于保存算式表达式字符串char szExpression[1024];//用于保存经过校验的算式表达式字符串char szCheckedExpression[1024];//用于保存计算结果的字符串char szResult[1024];//控件字体设置HFONT hCtlFont;//用于存储双精度格式的结果double ResultDate;//标记是否出现错误bool tagError;//记录错误信息char szErrorMessage[1024];//背景图片HBITMAP hBackGroundBitmap;public:Calculator();~Calculator();//初始化,用于创建按键控件和显示控件void Initialize(HWND hWnd);//相应键盘输入转换成统一的指令(鼠标点击按键)void OnCommand(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);//相应键盘输入转换成统一的指令(数字按键和运算符号按键)void OnChar(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);//相应键盘输入转换成统一的指令(其他特殊按键)void OnKeyDown(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);//根据用户的输入指令进行相应的处理void OnExcuteString(HWND hWnd,char *szCommand);//屏幕显示内容void OnPaint(HWND hWnd,HDC hDC);//根据字符串计算结果double GetResultValueByString();//计算分步结果void CalValue(Stack<double> *&ptNumStack,Stack<char> *&ptOperatorStack);//逆波兰算法实现double Polish(char *String, int len);};//自定义计算器类实例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、初始化及界面初始化
我们这里分别采用系统CreateWindow函数创建的按键BUTTON控件和STATIC控件,分别来响应用户输入和输出。
void Calculator::Initialize(HWND hWnd)
{//控件字体设置HFONT hCtlFont=CreateFont(22,0,0,0,1000,0,0,0,0,0,0,PROOF_QUALITY,0,"宋体");//获取窗口的大小RECT tempClientRect;GetClientRect(hWnd,&tempClientRect);//自定义按键的文字标题char szButtonTitle[BUTTONMAXNUM][1024]={".","0","C","+","1","2","3","-","4","5","6","*","7","8","9","/","(",")","DEL","="};//创建按键控件,并设置按键的位置和标题for(int i=0;i<BUTTONMAXNUM;i++){//设置按键的宽和高int w=60,h=35,gap=10;//设置按键的坐标位置int x=10+(i%4)*(w+gap),y=tempClientRect.bottom-h-gap-(i/4)*(h+gap);//创建按键子控件CreateWindow("BUTTON",szButtonTitle[i],WS_CHILD|WS_VISIBLE|WS_CLIPCHILDREN|WS_CLIPCHILDREN|WS_CLIPSIBLINGS,x,y,60,35,hWnd,(HMENU)i,NULL,NULL);//设置字体记大小SendMessage(GetDlgItem(hWnd,i),WM_SETFONT,(WPARAM)hCtlFont,1);}//创建显示子控件,算式显示屏幕CreateWindowEx(WS_EX_CLIENTEDGE,"STATIC","",WS_CHILD|WS_VISIBLE|SS_RIGHT|SS_CENTERIMAGE,10,10,tempClientRect.right-20,50,hWnd,(HMENU)51,NULL,NULL);//创建显示子控件,结果显示屏幕CreateWindowEx(WS_EX_CLIENTEDGE,"STATIC","",WS_CHILD|WS_VISIBLE|SS_RIGHT|SS_CENTERIMAGE,10,70,tempClientRect.right-20,50,hWnd,(HMENU)53,NULL,NULL);//设置字体记大小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、计算器消息处理逻辑
在这里,我们设计鼠标操作和键盘同时可以操作计算器,因此我们需要统一两种操作的模式。我们将WM_CHAR、WM_KEYDOWN和WM_COMMAND的消息统一转换成Calculator类的指令。
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)
{//判断是否响应相应的按键bool tagResponseStatus=false;//当用户按下数字键,包括小键盘的数字键if('0'<=LOWORD(wParam) && LOWORD(wParam)<='9'){tagResponseStatus=true;}//当按下加、减、乘、除按键时响应if(LOWORD(wParam)==43 || LOWORD(wParam)==45 || LOWORD(wParam)==42 || LOWORD(wParam)==47){tagResponseStatus=true;}//当按下左括号、右括号、小数点键if(LOWORD(wParam)==40 || LOWORD(wParam)==41 || LOWORD(wParam)==46){tagResponseStatus=true;}//对设置的按键命令进行响应if(tagResponseStatus==true){char szCommand[1024]="";sprintf(szCommand,"%c",LOWORD(wParam));OnExcuteString(hWnd,szCommand);}//当按下回车、ESC、等号、BACKSPACE键执行相应的指令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)
{//键盘按下DEL键执行清空显示控件的指令if(LOWORD(wParam)==46){OnExcuteString(hWnd,"C");}}//屏幕显示内容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);}//调试信息,防止内存泄露if(!true){char szTemp[1024]="";sprintf(szTemp,"st_StackNodeNum:%d",st_StackNodeNum);TextOut(hDC,10,120,szTemp,strlen(szTemp));}}
4.3.2、用户自定义输入表达式逻辑
在计算器字符表达式的输入过程中,我们需要用户根据一定的规则去输入中缀表达式,而不能任意输入错误的表达式,我们会在用户输入时加上一些必要的校验,比如不能连续出现两个运算符号,括号需要成对出现等等。
void Calculator::OnExcuteString(HWND hWnd,char *szCommand)
{//每次输入时重置错误信息tagError=false;strcpy(szErrorMessage,"");//如果当前需要添加的是数字if(IsNumber(szCommand)==true){if(strlen(szExpression)>0 && strlen(szExpression)<500){//获取表达式的最后一个字符并转换为字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//如果末尾不为右括号则直接添加if(strcmp(szEndChar,")")!=0){strcat(szExpression,szCommand);}}else{strcat(szExpression,szCommand);}}//如果当前需要添加的是运算符if(IsOperator(szCommand)==true){if(strlen(szExpression)>0 && strlen(szExpression)<500){//获取表达式的最后一个字符并转换为字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//判断字符串结尾字符是否为数字if(IsNumber(szEndChar)==true || strcmp(szEndChar,")")==0 || strcmp(szEndChar,".")==0){strcat(szExpression,szCommand);}//如果末尾字符为运算符,删除用新的运算符替换旧的运算符else if(IsOperator(szEndChar)==true){//在新的字符串中进行操作char szNewExpression[1024]="";//拷贝到新的字符串进行操作strcpy(szNewExpression,szExpression);//删除一个字符szNewExpression[strlen(szNewExpression)-1]='\0';//添加新的运算符号strcat(szNewExpression,szCommand);//拷贝新的字符串到原算式表达式字符串strcpy(szExpression,szNewExpression);}}}//左括号的输入if(strcmp(szCommand,"(")==0){if(strlen(szExpression)<500){//获取表达式的最后一个字符并转换为字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//末尾字符为运算数字运算符的替换,为数字(或其他)的则直接添加if(strlen(szExpression)==0 || IsOperator(szEndChar)==true || strcmp(szEndChar,"(")==0){strcat(szExpression,szCommand);}else{MessageBeep(MB_OK);}}}//右括号的输入if(strcmp(szCommand,")")==0){if(strlen(szExpression)<500){//获取表达式的最后一个字符并转换为字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//判断字符串结尾字符是否为数字if((IsNumber(szEndChar)==true || strcmp(szEndChar,")")==0) && GetCharAmount(szExpression,'(')>GetCharAmount(szExpression,')')){strcat(szExpression,szCommand);} else{MessageBeep(MB_OK);} }}//如果前一个字符是数字,则直接添加到算式中if(strcmp(szCommand,".")==0){if(strlen(szExpression)<500){//获取表达式的最后一个字符并转换为字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//判断字符串结尾字符是否为数字if(IsNumber(szEndChar)==true){strcat(szExpression,szCommand);}}}//如果是数字,则直接添加到算式显示控件if(strcmp(szCommand,"C")==0){strcpy(szExpression,"");strcpy(szCheckedExpression,"");strcpy(szResult,"");}//如果是数字,则直接添加到算式显示控件if(strcmp(szCommand,"DEL")==0){if(strlen(szExpression)>0){//获取表达式的最后一个字符并转换为字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//在新的字符串中进行操作char szNewExpression[1024]="";//拷贝到新的字符串进行操作strcpy(szNewExpression,szExpression);//删除一个字符szNewExpression[strlen(szNewExpression)-1]='\0';//拷贝新的字符串到原算式表达式字符串strcpy(szExpression,szNewExpression);}else{MessageBeep(MB_OK);}}//根据用户输入的字符串表达式智能纠错后计算结果GetResultValueByString();//当按下等于号对结果进行交换保存if(strcmp(szCommand,"=")==0){if(tagError!=true){//将结果保存到算式表达式字符串,并重置其他字符串strcpy(szExpression,szResult);strcpy(szCheckedExpression,"");strcpy(szResult,"");}else{MessageBeep(MB_OK);}}//更新显示“ 字符串显示框”SetWindowText(GetDlgItem(hWnd,51),szExpression);//更新显示校验后的“ 字符串显示框”SetWindowText(GetDlgItem(hWnd,52),szCheckedExpression);//更新显示“ 字符串显示框”SetWindowText(GetDlgItem(hWnd,53),szResult);//更新界面//InvalidateRect(hWnd,NULL,false);}
4.3.3、逆波兰计算数学表达式
采用将用户自定义输入的中缀表达式字符串,通过栈的方式转换为后缀表达式的算法,即逆波兰方法及时并返回结果。
void Calculator::CalValue(Stack<double> *&ptNumStack,Stack<char> *&ptOperatorStack)
{double NumberLeft, NumberRight, NumberResult;char Operator;//将栈顶的两个数字和一个操作符进行出栈操作PopStack(ptNumStack, NumberRight);PopStack(ptNumStack, NumberLeft);PopStack(ptOperatorStack, Operator);//记录除数为零的情况if(NumberRight==0 || NumberLeft==0){tagError=true;strcpy(szErrorMessage,"错误:除数不能为零");}//对出栈的两个数字和一个操作符进行计算if (Operator == '+')NumberResult = NumberLeft + NumberRight;if (Operator == '-')NumberResult = NumberLeft - NumberRight;if (Operator == '*')NumberResult = NumberLeft * NumberRight;if (Operator == '/')NumberResult = NumberLeft / NumberRight;//将计算后的结果继续押入栈中PushStack(ptNumStack, NumberResult);}//逆波兰算法实现double Calculator::Polish(char *String, int len)
{Stack<double> *ptNumStack;Stack<char> *ptOperatorStack;//初始栈,最主要产生一个默认的节点InitStack(ptNumStack);InitStack(ptOperatorStack);//逐字符读取字符串的游标位置int index = 0;//逐字符读取字符串while(!IsStackEmpty(ptOperatorStack) || index<len){//当前游标位置小于字符串长度时,逐个获取数字和运算符号并进行运算;否则做收尾工作if(index<len){//如果当前游标位置为数字,则说明这里是我们需要读取数字的开始位置if((String[index] >= '0' && String[index] <= '9') || String[index]=='.'){//将此后的数字区域读取到临时字符串char szTempNum[100]="";int iPos=0;//循环取得数字,当遇到第一个不是数字或小数点的字符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';//获取到我们需要的数字,并将数字保存到栈中double tempNumber=atof(szTempNum);PushStack(ptNumStack, tempNumber);}else {//如果当前字符串为运算符,则根据运行符的种类进行判断if (String[index] == '(' || (GetStackTopValue(ptOperatorStack) == '(' && String[index] != ')') || IsStackEmpty(ptOperatorStack)){//遇到以上情况,则直接将运行符保存的符号栈中PushStack(ptOperatorStack, String[index++]);}else if (String[index] == '+' || String[index] == '-'){//如果遇到加、减号,就把此前已入栈的算式进行计算,并将结果结果和符号重新入栈while (GetStackTopValue(ptOperatorStack) != '(' && !IsStackEmpty(ptOperatorStack)){CalValue(ptNumStack, ptOperatorStack);}PushStack(ptOperatorStack, String[index++]);}else if (String[index] == '*' || String[index] == '/') {//如果遇到乘、除号,则把之前所有乘、除相关的算式进行计算,并将结果结果和符号重新入栈while (GetStackTopValue(ptOperatorStack) == '*' || GetStackTopValue(ptOperatorStack) == '/'){CalValue(ptNumStack, ptOperatorStack);}PushStack(ptOperatorStack, String[index++]);}else if (String[index] == ')'){//当遇到有括号,则把所有括号对内的算式计算完毕,右括号无需入栈while(GetStackTopValue(ptOperatorStack) != '(') {//当栈为空时无需进行计算跳出循环if(IsStackEmpty(ptOperatorStack))break;CalValue(ptNumStack, ptOperatorStack);}//当计算完所有括号内容的算式,弹出对应的左括号char tempBracket;PopStack(ptOperatorStack, tempBracket);index++;}}}else{//遍历完字符串所有字符后,只需对还未空的运算符栈进行逐步计算CalValue(ptNumStack, ptOperatorStack);}}//最后栈顶(根节点的下一个节点)的数据就是表达式的结果double NumberValue=0;PopStack(ptNumStack,NumberValue);//在删除所有子节点后销毁栈的根节点DestroyStack(ptNumStack);DestroyStack(ptOperatorStack);//返回计算的结果return NumberValue;}
4.3.3、更新及显示结果
前期已经通过键盘或鼠标消息处理,在szExpression中输入了用户自定义算式表达式,因此,我们直接在这里进行计算,并将最终的结果反馈到szResult字符串。并通过类的流程控制在显示控件中显示出来。
//根据字符串计算结果double Calculator::GetResultValueByString()
{//校验用户的输入,进行自动纠错处理strcpy(szCheckedExpression,szExpression);//对客户输入的算式进行自动纠错处理if(strlen(szCheckedExpression)!=0){//自动去除末尾的非数字符号for(int i=strlen(szCheckedExpression)-1;i>=0;i--){//获取表达式的最后一个字符并转换为字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szCheckedExpression[strlen(szCheckedExpression)-1]);//智能删除用户算式表达式的末尾运算符号和左括号if(IsNumber(szEndChar)==false && strcmp(szEndChar,")")!=0 && strcmp(szEndChar,".")!=0){szCheckedExpression[i]='\0';}else{break;}}//自动添加用户应加未加的右括号int iTempBracketNum=GetCharAmount(szCheckedExpression,'(')-GetCharAmount(szCheckedExpression,')');//自动添加用户应加未加的右括号for(int j=0;j<iTempBracketNum;j++){strcat(szCheckedExpression,")");}//将纠错后的字符串进行计算处理if(strlen(szCheckedExpression)!=0){//根据纠错后的算式字符串计算结果ResultDate=Polish(szCheckedExpression,strlen(szCheckedExpression));//显示出结果sprintf(szResult,"%0.10f",ResultDate);//删除小数部分末尾的零TrimBackZero(szResult);//如果出现错误,则只显示错误信息if(tagError==true){strcpy(szResult,szErrorMessage);}}}else{//用户自定义字符串为空时,重置结果字符串strcpy(szCheckedExpression,"");strcpy(szResult,"");}return 0;}
4.4、主窗口函数及消息循环
负责程序主窗口的创建,以及消息函数的集中分发处理。这里由于存在子按键控件,因此鼠标点击按键后,主窗口将无法收到键盘消息WM_CHAR和WM_KEYDIWN消息,导致键盘输入失效,我们这里采用在主消息循环中,复制子窗口WM_CHAR和WM_KEYDIWN消息并手动转发给游戏主窗口的方法予以解决。同时在使用键盘过程中要注意,在中文输入法时键盘输入受到一定影响,需要手动切换输入法。另外,小键盘锁也会影响到小键盘的输入。
//消息处理模块LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{HDC hDC=NULL;switch(message){case WM_CREATE://初始化并创建按键、算式显示框和结果显示框Calculators.Initialize(hWnd);return 0;case WM_PAINT:PAINTSTRUCT PS; hDC=BeginPaint(hWnd,&PS);//显示屏幕内容Calculators.OnPaint(hWnd,hDC);ReleaseDC(hWnd,hDC);return 0;case WM_COMMAND://根据消息执行计算器的操作Calculators.OnCommand(hWnd,message,wParam,lParam);return 0;case WM_CHAR://根据消息执行计算器的操作Calculators.OnChar(hWnd,message,wParam,lParam);return 0;case WM_KEYDOWN://根据消息执行计算器的操作Calculators.OnKeyDown(hWnd,message,wParam,lParam);return 0;case WM_DESTROY:PostQuitMessage(0);return 0;}return DefWindowProc(hWnd,message,wParam,lParam);}//
//Calculator计算器经典版
//作者:zhooyu
//2024.12.7
//CSDN主页地址:https://blog.csdn.net/zhooyu
//CSDN文章地址:https://blog.csdn.net/zhooyu/article/details/144202897
////主函数int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR szCmdLine,int iCmdShow)
{MSG msg;HWND hWnd;CHAR szAppName[]="Calculator";//设置程序的样式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;}//创建窗口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);//消息循环while(GetMessage(&msg,NULL,0,0)){TranslateMessage(&msg);DispatchMessage(&msg);//调试信息if(msg.message==WM_CHAR || msg.message==WM_KEYDOWN){//调试信息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、完整源码
该项目代码仅仅包括一个cpp源文件,新建项目直接运行,无需在资源编辑器中创建按键、显示框等控件资源。可以在VS2010和VC6.0中新建项目后直接运行。这里将全部源代码整理如下,供大家参考。整理代码不易,请大家不吝点赞关注,如果能留言就再好不过了,您的支持是我继续前进的动力!!谢谢了先。
//加载系统头文件#include "windows.h"#include "stdio.h"#include "math.h"//节点统计数字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--;}}//判断栈是否为空,除去没有存数据的首个节点外template<typename Type>bool IsStackEmpty(Stack<Type>* Node)
{return Node->ptNext == NULL;}//获取栈顶部节点的数据template<typename Type>Type GetStackTopValue(Stack<Type>* Node)
{if(Node->ptNext !=NULL){return Node->ptNext->num;}else{return 0;}}//省略掉数字的小数点后末尾多余的零void TrimBackZero(char *szString)
{//标记小数点的位置int iDotPos=-1;//先找到小数点的位置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;}}}//获取字符串中某个字符的个数int GetCharAmount(char *szString,char sign)
{int iAmount=0;for(int i=0;i<lstrlen(szString);i++){if(szString[i]==sign)iAmount++;}return iAmount;}//判断是否为数字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;}//判断是否为运算符号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;}//按键最大数量#define BUTTONMAXNUM 20//计算器类class Calculator
{public://用于保存算式表达式字符串char szExpression[1024];//用于保存经过校验的算式表达式字符串char szCheckedExpression[1024];//用于保存计算结果的字符串char szResult[1024];//控件字体设置HFONT hCtlFont;//用于存储双精度格式的结果double ResultDate;//标记是否出现错误bool tagError;//记录错误信息char szErrorMessage[1024];//背景图片HBITMAP hBackGroundBitmap;public:Calculator();~Calculator();//初始化,用于创建按键控件和显示控件void Initialize(HWND hWnd);//相应键盘输入转换成统一的指令(鼠标点击按键)void OnCommand(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);//相应键盘输入转换成统一的指令(数字按键和运算符号按键)void OnChar(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);//相应键盘输入转换成统一的指令(其他特殊按键)void OnKeyDown(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);//根据用户的输入指令进行相应的处理void OnExcuteString(HWND hWnd,char *szCommand);//屏幕显示内容void OnPaint(HWND hWnd,HDC hDC);//根据字符串计算结果double GetResultValueByString();//计算分步结果void CalValue(Stack<double> *&ptNumStack,Stack<char> *&ptOperatorStack);//逆波兰算法实现double Polish(char *String, int len);};//自定义计算器类实例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)
{//控件字体设置HFONT hCtlFont=CreateFont(22,0,0,0,1000,0,0,0,0,0,0,PROOF_QUALITY,0,"宋体");//获取窗口的大小RECT tempClientRect;GetClientRect(hWnd,&tempClientRect);//自定义按键的文字标题char szButtonTitle[BUTTONMAXNUM][1024]={".","0","C","+","1","2","3","-","4","5","6","*","7","8","9","/","(",")","DEL","="};//创建按键控件,并设置按键的位置和标题for(int i=0;i<BUTTONMAXNUM;i++){//设置按键的宽和高int w=60,h=35,gap=10;//设置按键的坐标位置int x=10+(i%4)*(w+gap),y=tempClientRect.bottom-h-gap-(i/4)*(h+gap);//创建按键子控件CreateWindow("BUTTON",szButtonTitle[i],WS_CHILD|WS_VISIBLE|WS_CLIPCHILDREN|WS_CLIPCHILDREN|WS_CLIPSIBLINGS,x,y,60,35,hWnd,(HMENU)i,NULL,NULL);//设置字体记大小SendMessage(GetDlgItem(hWnd,i),WM_SETFONT,(WPARAM)hCtlFont,1);}//创建显示子控件,算式显示屏幕CreateWindowEx(WS_EX_CLIENTEDGE,"STATIC","",WS_CHILD|WS_VISIBLE|SS_RIGHT|SS_CENTERIMAGE,10,10,tempClientRect.right-20,50,hWnd,(HMENU)51,NULL,NULL);//创建显示子控件,结果显示屏幕CreateWindowEx(WS_EX_CLIENTEDGE,"STATIC","",WS_CHILD|WS_VISIBLE|SS_RIGHT|SS_CENTERIMAGE,10,70,tempClientRect.right-20,50,hWnd,(HMENU)53,NULL,NULL);//设置字体记大小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)
{//判断是否响应相应的按键bool tagResponseStatus=false;//当用户按下数字键,包括小键盘的数字键if('0'<=LOWORD(wParam) && LOWORD(wParam)<='9'){tagResponseStatus=true;}//当按下加、减、乘、除按键时响应if(LOWORD(wParam)==43 || LOWORD(wParam)==45 || LOWORD(wParam)==42 || LOWORD(wParam)==47){tagResponseStatus=true;}//当按下左括号、右括号、小数点键if(LOWORD(wParam)==40 || LOWORD(wParam)==41 || LOWORD(wParam)==46){tagResponseStatus=true;}//对设置的按键命令进行响应if(tagResponseStatus==true){char szCommand[1024]="";sprintf(szCommand,"%c",LOWORD(wParam));OnExcuteString(hWnd,szCommand);}//当按下回车、ESC、等号、BACKSPACE键执行相应的指令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)
{//键盘按下DEL键执行清空显示控件的指令if(LOWORD(wParam)==46){OnExcuteString(hWnd,"C");}}//屏幕显示内容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);}//调试信息,防止内存泄露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)
{//每次输入时重置错误信息tagError=false;strcpy(szErrorMessage,"");//如果当前需要添加的是数字if(IsNumber(szCommand)==true){if(strlen(szExpression)>0 && strlen(szExpression)<500){//获取表达式的最后一个字符并转换为字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//如果末尾不为右括号则直接添加if(strcmp(szEndChar,")")!=0){strcat(szExpression,szCommand);}}else{strcat(szExpression,szCommand);}}//如果当前需要添加的是运算符if(IsOperator(szCommand)==true){if(strlen(szExpression)>0 && strlen(szExpression)<500){//获取表达式的最后一个字符并转换为字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//判断字符串结尾字符是否为数字if(IsNumber(szEndChar)==true || strcmp(szEndChar,")")==0 || strcmp(szEndChar,".")==0){strcat(szExpression,szCommand);}//如果末尾字符为运算符,删除用新的运算符替换旧的运算符else if(IsOperator(szEndChar)==true){//在新的字符串中进行操作char szNewExpression[1024]="";//拷贝到新的字符串进行操作strcpy(szNewExpression,szExpression);//删除一个字符szNewExpression[strlen(szNewExpression)-1]='\0';//添加新的运算符号strcat(szNewExpression,szCommand);//拷贝新的字符串到原算式表达式字符串strcpy(szExpression,szNewExpression);}}}//左括号的输入if(strcmp(szCommand,"(")==0){if(strlen(szExpression)<500){//获取表达式的最后一个字符并转换为字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//末尾字符为运算数字运算符的替换,为数字(或其他)的则直接添加if(strlen(szExpression)==0 || IsOperator(szEndChar)==true || strcmp(szEndChar,"(")==0){strcat(szExpression,szCommand);}else{MessageBeep(MB_OK);}}}//右括号的输入if(strcmp(szCommand,")")==0){if(strlen(szExpression)<500){//获取表达式的最后一个字符并转换为字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//判断字符串结尾字符是否为数字if((IsNumber(szEndChar)==true || strcmp(szEndChar,")")==0) && GetCharAmount(szExpression,'(')>GetCharAmount(szExpression,')')){strcat(szExpression,szCommand);} else{MessageBeep(MB_OK);} }}//如果前一个字符是数字,则直接添加到算式中if(strcmp(szCommand,".")==0){if(strlen(szExpression)<500){//获取表达式的最后一个字符并转换为字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//判断字符串结尾字符是否为数字if(IsNumber(szEndChar)==true){strcat(szExpression,szCommand);}}}//如果是数字,则直接添加到算式显示控件if(strcmp(szCommand,"C")==0){strcpy(szExpression,"");strcpy(szCheckedExpression,"");strcpy(szResult,"");}//如果是数字,则直接添加到算式显示控件if(strcmp(szCommand,"DEL")==0){if(strlen(szExpression)>0){//获取表达式的最后一个字符并转换为字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szExpression[strlen(szExpression)-1]);//在新的字符串中进行操作char szNewExpression[1024]="";//拷贝到新的字符串进行操作strcpy(szNewExpression,szExpression);//删除一个字符szNewExpression[strlen(szNewExpression)-1]='\0';//拷贝新的字符串到原算式表达式字符串strcpy(szExpression,szNewExpression);}else{MessageBeep(MB_OK);}}//根据用户输入的字符串表达式智能纠错后计算结果GetResultValueByString();//当按下等于号对结果进行交换保存if(strcmp(szCommand,"=")==0){if(tagError!=true){//将结果保存到算式表达式字符串,并重置其他字符串strcpy(szExpression,szResult);strcpy(szCheckedExpression,"");strcpy(szResult,"");}else{MessageBeep(MB_OK);}}//更新显示“ 字符串显示框”SetWindowText(GetDlgItem(hWnd,51),szExpression);//更新显示校验后的“ 字符串显示框”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;//将栈顶的两个数字和一个操作符进行出栈操作PopStack(ptNumStack, NumberRight);PopStack(ptNumStack, NumberLeft);PopStack(ptOperatorStack, Operator);//记录除数为零的情况if(NumberRight==0 || NumberLeft==0){tagError=true;strcpy(szErrorMessage,"错误:除数不能为零");}//对出栈的两个数字和一个操作符进行计算if (Operator == '+')NumberResult = NumberLeft + NumberRight;if (Operator == '-')NumberResult = NumberLeft - NumberRight;if (Operator == '*')NumberResult = NumberLeft * NumberRight;if (Operator == '/')NumberResult = NumberLeft / NumberRight;//将计算后的结果继续押入栈中PushStack(ptNumStack, NumberResult);}//逆波兰算法实现double Calculator::Polish(char *String, int len)
{Stack<double> *ptNumStack;Stack<char> *ptOperatorStack;//初始栈,最主要产生一个默认的节点InitStack(ptNumStack);InitStack(ptOperatorStack);//逐字符读取字符串的游标位置int index = 0;//逐字符读取字符串while(!IsStackEmpty(ptOperatorStack) || index<len){//当前游标位置小于字符串长度时,逐个获取数字和运算符号并进行运算;否则做收尾工作if(index<len){//如果当前游标位置为数字,则说明这里是我们需要读取数字的开始位置if((String[index] >= '0' && String[index] <= '9') || String[index]=='.'){//将此后的数字区域读取到临时字符串char szTempNum[100]="";int iPos=0;//循环取得数字,当遇到第一个不是数字或小数点的字符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';//获取到我们需要的数字,并将数字保存到栈中double tempNumber=atof(szTempNum);PushStack(ptNumStack, tempNumber);}else {//如果当前字符串为运算符,则根据运行符的种类进行判断if (String[index] == '(' || (GetStackTopValue(ptOperatorStack) == '(' && String[index] != ')') || IsStackEmpty(ptOperatorStack)){//遇到以上情况,则直接将运行符保存的符号栈中PushStack(ptOperatorStack, String[index++]);}else if (String[index] == '+' || String[index] == '-'){//如果遇到加、减号,就把此前已入栈的算式进行计算,并将结果结果和符号重新入栈while (GetStackTopValue(ptOperatorStack) != '(' && !IsStackEmpty(ptOperatorStack)){CalValue(ptNumStack, ptOperatorStack);}PushStack(ptOperatorStack, String[index++]);}else if (String[index] == '*' || String[index] == '/') {//如果遇到乘、除号,则把之前所有乘、除相关的算式进行计算,并将结果结果和符号重新入栈while (GetStackTopValue(ptOperatorStack) == '*' || GetStackTopValue(ptOperatorStack) == '/'){CalValue(ptNumStack, ptOperatorStack);}PushStack(ptOperatorStack, String[index++]);}else if (String[index] == ')'){//当遇到有括号,则把所有括号对内的算式计算完毕,右括号无需入栈while(GetStackTopValue(ptOperatorStack) != '(') {//当栈为空时无需进行计算跳出循环if(IsStackEmpty(ptOperatorStack))break;CalValue(ptNumStack, ptOperatorStack);}//当计算完所有括号内容的算式,弹出对应的左括号char tempBracket;PopStack(ptOperatorStack, tempBracket);index++;}}}else{//遍历完字符串所有字符后,只需对还未空的运算符栈进行逐步计算CalValue(ptNumStack, ptOperatorStack);}}//最后栈顶(根节点的下一个节点)的数据就是表达式的结果double NumberValue=0;PopStack(ptNumStack,NumberValue);//在删除所有子节点后销毁栈的根节点DestroyStack(ptNumStack);DestroyStack(ptOperatorStack);//返回计算的结果return NumberValue;}//根据字符串计算结果double Calculator::GetResultValueByString()
{//校验用户的输入,进行自动纠错处理strcpy(szCheckedExpression,szExpression);//对客户输入的算式进行自动纠错处理if(strlen(szCheckedExpression)!=0){//自动去除末尾的非数字符号for(int i=strlen(szCheckedExpression)-1;i>=0;i--){//获取表达式的最后一个字符并转换为字符串char szEndChar[10]="";sprintf(szEndChar,"%c",szCheckedExpression[strlen(szCheckedExpression)-1]);//智能删除用户算式表达式的末尾运算符号和左括号if(IsNumber(szEndChar)==false && strcmp(szEndChar,")")!=0 && strcmp(szEndChar,".")!=0){szCheckedExpression[i]='\0';}else{break;}}//自动添加用户应加未加的右括号int iTempBracketNum=GetCharAmount(szCheckedExpression,'(')-GetCharAmount(szCheckedExpression,')');//自动添加用户应加未加的右括号for(int j=0;j<iTempBracketNum;j++){strcat(szCheckedExpression,")");}//将纠错后的字符串进行计算处理if(strlen(szCheckedExpression)!=0){//根据纠错后的算式字符串计算结果ResultDate=Polish(szCheckedExpression,strlen(szCheckedExpression));//显示出结果sprintf(szResult,"%0.10f",ResultDate);//删除小数部分末尾的零TrimBackZero(szResult);//如果出现错误,则只显示错误信息if(tagError==true){strcpy(szResult,szErrorMessage);}}}else{//用户自定义字符串为空时,重置结果字符串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://初始化并创建按键、算式显示框和结果显示框Calculators.Initialize(hWnd);return 0;case WM_PAINT:PAINTSTRUCT PS; hDC=BeginPaint(hWnd,&PS);//显示屏幕内容Calculators.OnPaint(hWnd,hDC);ReleaseDC(hWnd,hDC);return 0;case WM_COMMAND://根据消息执行计算器的操作Calculators.OnCommand(hWnd,message,wParam,lParam);return 0;case WM_CHAR://根据消息执行计算器的操作Calculators.OnChar(hWnd,message,wParam,lParam);return 0;case WM_KEYDOWN://根据消息执行计算器的操作Calculators.OnKeyDown(hWnd,message,wParam,lParam);return 0;case WM_DESTROY:PostQuitMessage(0);return 0;}return DefWindowProc(hWnd,message,wParam,lParam);}//
//Calculator计算器经典版
//作者:zhooyu
//2024.12.7
//CSDN主页地址:https://blog.csdn.net/zhooyu
//CSDN文章地址:https://blog.csdn.net/zhooyu/article/details/144202897
////主函数int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR szCmdLine,int iCmdShow)
{MSG msg;HWND hWnd;CHAR szAppName[]="Calculator";//设置程序的样式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;}//创建窗口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);//消息循环while(GetMessage(&msg,NULL,0,0)){TranslateMessage(&msg);DispatchMessage(&msg);//调试信息if(msg.message==WM_CHAR || msg.message==WM_KEYDOWN){//调试信息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中无差异运行,因此就上传了两个版本的源码,方便运行。
6.1、VS2010源码下载
CSDN下载地址:Calculator20241207-15-vs2010.rar
6.2、VC6.0源码下载
CSDN下载地址:Calculator20241207-15-vc6.0.rar
相关文章:

C++实现一个经典计算器(逆波兰算法)附源码
1、本篇要实现的内容 最近,大家讨论计算器的实现比较热,今天我也来用C和Visual Studio实现一个计算器的小程序。这里使用逆波兰算法,能够根据当前用户输入的算式表达式字符串,计算出所要的结果,算式字符串可以包括加、…...
Python知识分享第二十二天-数据结构入门
数据结构 “”" 基础概念: 程序 数据结构 算法 数据结构 存储和组织数据的方式. 算法 解决问题的思维, 思路, 方式. 算法的特性:独立性: 算法 思维, 是解决问题的思路和方式, 不依赖语言.5大特性: 有输入, 有输出, 有穷性, 确定性, 可行性.问: 如何衡量算法的优劣?…...

【WRF理论第十三期】详细介绍 Registry 的作用、结构和内容
目录 1. Introduction:介绍 Registry 的作用和功能。2. Registry Contents:详细描述 Registry 的结构和内容,包括各个部分的条目类型。2.1. DIMSPEC ENTRIES(维度规格条目)2.2. STATE ENTRIES(状态变量条目…...
Android启动优化指南
文章目录 前言一、启动分类与优化目标1、冷启动1.1 优化思路1.2 延迟初始化与按需加载1.3 并行加载与异步执行1.4 资源优化与懒加载1.5 内存优化与垃圾回收控制 2. 温启动2.1 优化应用的生命周期管理2.2 数据缓存与懒加载2.3 延迟渲染与视图优化 3. 热启动3.1 保持应用的状态3.…...
【ETCD】【源码阅读】configureClientListeners () 函数解析
逐步解析 configureClientListeners 函数 configureClientListeners 是 ETCD 的一个重要函数,用于配置客户端通信的监听器(Client Listeners)。这些监听器主要负责处理外部客户端与 ETCD 服务之间的通信,包括 HTTP 和 gRPC 请求。…...

IO进程学习笔记
man手册 普通命令。系统调用的函数。库函数。特殊文件。文件格式。游戏。附加的一些变量 IO介绍 I:input 输入 O:output 输出 对文件的输入和输出 输入-》写文件,将文件中的内容写到内存中去 输出-》读文件,将内存中的内容读取到文…...
智能手机回暖:华为点火,小米荣耀OV拱火
进入11月中下旬,智能手机圈再度热闹起来。包括华为、小米、OPPO、vivo等诸多手机厂商,都在陆续预热发布新机,其中就包括华为Mate 70、小米Redmi K80、vivo的S20,IQOO Neo10等热门新机,这些热门新机的集中上市迅速吸引了…...

Sqoop导入数据(mysql---->>hive)
目录 数据传输流程脚本报错和异常说明1. Caused by: java.lang.ClassNotFoundException: org.apache.hadoop.hive.conf.HiveConf2. 数据导入hive后显示NULL 数据传输流程 mysql---->>hdfs---->>hive 数据从mysql表中取出,放到hdfs上(由targ…...

实验3-实时数据流处理-Flink
1.前期准备 (1)Flink基础环境安装 参考文章: 利用docker-compose来搭建flink集群-CSDN博客 显示为这样就成功了 (2)把docker,docker-compose,kafka集群安装配置好 参考文章: …...

深度学习实验十四 循环神经网络(1)——测试简单循环网络的记忆能力
目录 一、数据集构建 1.1数据集的构建函数 1.2加载数据集并划分 1.3 构建Dataset类 二、模型构建 2.1嵌入层 2.2SRN层 2.3模型汇总 三、模型训练 3.1 训练指定长度的数字预测模型 3.2 损失曲线展示 四、模型评价 五、修改 附完整可运行代码 实验大体步骤&#x…...

k8s部署odoo18(kubeshpere面板)
Postgresql部署 链接: kubesphere搭建 postgres15 因为我的是在另一台服务器使用kubesphere进行部署的,如果有和我一样情况的,可以参考上面的文档部署postgreasql。 注意事项: 因为odoo不允许使用postgresql的默认用户,也就是po…...

【模型对比】ChatGPT vs Kimi vs 文心一言那个更好用?数据详细解析,找出最适合你的AI辅助工具!
在这个人工智能迅猛发展的时代,AI聊天助手已经深入我们的工作与生活。你是否曾在选择使用ChatGPT、Kimi或是百度的文心一言时感到一头雾水?每款AI都有其独特的魅力与优势,那么,究竟哪一款AI聊天助手最适合你呢?本文将带…...

Java——容器(单例集合)(上)
一 容器介绍 容器,是用来容纳物体、管理物体。生活中,我们会用到各种各样的容器。如锅碗瓢盆、箱子和包等 程序中的“容器”也有类似的功能,用来容纳和管理数据。比如,如下新闻网站的新闻列表、教育网站的课程列表就是用“容器”来管理 视频…...

如何配置Github并在本地提交代码
前提: 可以流畅访问github, 需要一些上网技巧, 这就自行处理了 申请一个github账号 Github官网地址 首先就是邮箱注册啦, github没有对邮箱的限制, 只要是能收邮件的就ok, qq邮箱, 163等都可以使用. 然后和普通注册账号一样, 一路填写需要的信息, 验证邮箱即可. 如何新增代…...
工作bug,keil5编译器,理解int 类型函数返回值问题,详解!!!
编写不易,禁止搬运,仅供学习,感谢理解 问题现象 下面是一个在keil5里面写的一个,int类型的返回值函数,这个函数里面,只有if else if else这三个判断条件语句,正常来说任何情况下,…...

简明速通Java接口
前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文从代码层面直接整理Java接口 让老油子们无需再理解繁杂的概念了。 Java接口在代码层面是做什么的 说白了老铁,Java的接口就是一个类,这个类中只能声明属性和方法,属性需要…...

MVC基础——市场管理系统(二)
文章目录 项目地址三、Produtcts的CRUD3.1 Products列表的展示页面(Read)3.1.1 给Product的Model里添加Category的属性3.1.2 View视图里展示Product List3.2 增加Product数据(Add)3.2.1 创建ViewModel用来组合多个Model3.2.2 在_ViewImposts里引入ViewModels3.2.3 添加Add的…...

java------------常用API preiod duration 计算时间差
1,preiod 如果末天数比初天数小,需要进一位 package API;import java.time.LocalDate; import java.time.Period;public class preiod {public static void main(String[] args) {// 计算时间差// LocalDate获取对象其中的一个方法LocalDate d1 LocalD…...
使用 FAISS 进行高效相似性搜索:从文本检索到动态数据处理
在现代数据科学和人工智能应用中,处理大量高维数据并从中找到相似项是一个常见任务。无论是在推荐系统、搜索引擎,还是在自然语言处理应用中,如何高效地进行相似性搜索(Similarity Search)一直是一个挑战。为了解决这个…...
执行“go mod tidy”遇到“misbehavior”错误
执行“go mod tidy”报错下错误,执行“go clean -modcache”和删除“go env GOMODCACHE”指定目录均无效: SECURITY ERROR go.sum database server misbehavior detected!old database:go.sum database tree3397826xyyhzdyAOat5li/EXx/MK1gONQf3LAGqArh…...

XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...

MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...

【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能
1. 开发环境准备 安装DevEco Studio 3.1: 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK 项目配置: // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...

从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障
关键领域软件测试的"安全密码":Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力,从金融交易到交通管控,这些关乎国计民生的关键领域…...

WebRTC调研
WebRTC是什么,为什么,如何使用 WebRTC有什么优势 WebRTC Architecture Amazon KVS WebRTC 其它厂商WebRTC 海康门禁WebRTC 海康门禁其他界面整理 威视通WebRTC 局域网 Google浏览器 Microsoft Edge 公网 RTSP RTMP NVR ONVIF SIP SRT WebRTC协…...

高端性能封装正在突破性能壁垒,其芯片集成技术助力人工智能革命。
2024 年,高端封装市场规模为 80 亿美元,预计到 2030 年将超过 280 亿美元,2024-2030 年复合年增长率为 23%。 细分到各个终端市场,最大的高端性能封装市场是“电信和基础设施”,2024 年该市场创造了超过 67% 的收入。…...