当前位置: 首页 > article >正文

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)。在数据结构中,使用栈的概念完成表达式的求值操作,在计算机系统处理表达式的计算过程中,将中缀表达式转换为后缀表达式的形式进行解析转换并实施计算,这就是逆波兰算法的应用。

具体实现方法大致为:

  1. 设两个栈,操作数栈和运算符栈;
  2. 操作数依次入操作数栈;
  3. 运算符入栈前与运算符的栈顶运算符比较优先级;
  4. 优先级高于栈顶运算符,压入栈,读入下一个符号;
  5. 优先级低于栈顶运算符,栈顶运算符出栈,操作数栈退出两个操作数,进行运算,结果压入操作数栈;
  6. 优先级相等,左右括号相遇,栈顶运算符出栈即可;
  7. 后缀表达式读完,栈顶为运算结果。

在这里插入图片描述

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


http://www.kler.cn/a/429651.html

相关文章:

  • unity——Preject3——面板基类
  • Windows安装ES单机版设置密码
  • 设计一个利用事务特性可以阻塞线程的排他锁,并且通过注解和 AOP 来实现
  • mysql的mvcc理解
  • MACPA:fMRI连接性分析的新工具
  • MySQL表的增删改查(基础)-下篇
  • Vue的生命周期方法有哪些?一般在哪一步发送请求
  • JavaWeb:HTMLCSS
  • 手机租赁平台开发的机会与挑战分析
  • win11 恢复任务栏copilot图标, 亲测有效
  • 数据分析Python转置文档代码
  • 分布式搜索引擎Elasticsearch(三)
  • 告别充电焦虑:移动充电机器人的革命性解决方案
  • 3D 目标检测:从萌芽到前沿的技术演进之路
  • linux间隔记录服务器的CPU占用率TOP10的进程
  • 使用Nexus3搭建Maven私有镜像仓库
  • Burp(8)-验证码爆破插件
  • 基于FPGA的PI环调节电压
  • Xcode编译的时候运行python脚本
  • 计算机网络——三大交换技术
  • 准确率99.9%的离线IP地址定位库 ip2region - python 示例
  • GauHuman阅读笔记【3D Human Modelling】
  • 知从科技闪耀汽车智能底盘大会:共探软件安全新篇章
  • ElasticSearch常见面试题汇总
  • 《maven 常用知识详解 3:聚合与继承》
  • Blender均匀放缩模型