MFC计算器——万字长文/一看就懂(需要的建议点赞收藏)

目录

 

一、创建VS文件

 二、对于计算器界面的创建

1.添加Edit Control

2.添加Static Text

3.添加Button

 三、对各按键的编写

1.事件处理函数

2.非特殊按键

3.特殊按键

(1)清零按键

(2)删除按键

(3)倒数运算

(4)等于号按键

四、运算函数的实现

1.Calculate.cpp文件

2.GetCalResult.cpp文件

3.transform.cpp文件

4.Calculator.h  文件(头文件)

五、计算示例

普通的复合运算

绝对值,倒数,次方的运算

小数的运算


 

一、创建VS文件

首先,打开vs,选择如图所示左边的MFC应用。更改项目名称并选择自己想存入的位置后,点击创建。

341639fcfcab450e874183cb7bcd3e88.png

点击创建后,在应用程序类型中选择“基于对话框”,如下图。

13190c186faf463eac9d0df0d5bc8f78.png

其他名称无需更改。可以在生成的类中更改类名,如下图,我改为MFC_Calculator。

1986af89c71b4b2fbdbd9fb4c676402a.png

点击完成后来到这个界面。屏幕左侧有“工具箱”。工具箱里面有我们会用到的一些组件。

2d8373ef3bcb4c03811863848094041e.png

 

 二、对于计算器界面的创建

我的MFC计算器界面如下,这个也是这篇文章的最终目标。

d44e2aefdcf64726a0b991ff95f6e547.png

可以看出,这个计算器具有整数和小数的加、减、乘、除等基本运算,也有倒数、次方、阶乘运算,以及有括号的复合运算,并具有清零(C),删除(Del)等功能。

接下来我们来一一创建。

1.添加Edit Control

在左侧的工具箱找到Edit Control组件,点击添加至我们的界面。

e586841c5a9f4a758eca8c87283e6f31.png

添加后,调整合适的大小。界面右下角的确定、取消按钮,可以单击右键,选择删除。调整完成后,如图

c74f8fc6127340138ebac1ddd53fbffe.png

 

2.添加Static Text

左侧工具箱中选择Static Text,添加至界面,并调整位置和大小。

41b2d1660b594f3fa369ea91d3ffce4c.png

此时,右键单击刚添加的Static Text,选择最下方的属性,将边框更改为True。如下图。

a8f2774550bb434cac8e7f3d9a2be81d.png

3.添加Button

将界面中间的“TODO:在此放置对话框控件。”右键单击删除。

左侧工具箱中选择Button,添加至界面。共添加24个Button。可以利用复制粘贴的方式快速添加。

23fad40284cd409bab299fcfac7a66aa.png

添加后可以在VS的工具栏中找到对齐方式快速对齐

44470511808e4b1fb4de4e1ef286a388.png

 然后一一更改每个Button的属性中的描述文字和ID

bf13891b99d941bba8b1c693c334a235.png

数字1 位于界面左下方,所以左下方的Button的描述文字改为“1”,ID我改为“IDC_BUTTON_1”。

 更改完成后如下图。

fc98421ac67144d89be12ba748d2676b.png

类似的,将其他Button的描述文字和ID进行更改。

以下为我对各按键的ID命名,Button的ID名称影响后续的编程。

2对应ID为IDC_BUTTON_2, 3对应ID为IDC_BUTTON_3,4对应ID为IDC_BUTTON_4                  5对应ID为IDC_BUTTON_5, 6对应ID为IDC_BUTTON_6,7对应ID为IDC_BUTTON_7                  8对应ID为IDC_BUTTON_8, 9对应ID为IDC_BUTTON_9,0对应ID为IDC_BUTTON_0            +对应ID为IDC_BUTTON_ADD, - 对应ID为IDC_BUTTON_SUB,*对应ID为IDC_BUTTON_MUL,    /对应ID为IDC_BUTTON_DIV,  |(绝对值符号)对应 ID为IDC_BUTTON_ABS,                              ^(次方)对应ID为IDC_BUTTON_POW,(对应ID为IDC_BUTTON_LBRACKET,                      )对应ID为IDC_BUTTON_RBRACKET,Del(删除按键)对应ID为IDC_BUTTON_DELETE,      C(清零按键)对应ID为IDC_BUTTON_CLEAR,1/x(倒数按键)对应ID为IDC_BUTTON_REM,!(阶乘按键)对应ID为IDC_BUTTON_FAC,.(小数点)对应ID为IDC_BUTTON_DOT,=(等于号)对应ID为IDC_BUTTON_EQUAL。

并将Edit Control的ID改为IDC_EDIT_EXP,Static Text的ID改为IDC_STATIC_RESULT

更改完成后,界面如下

fa38d03f66504210b8d50da459dc9a03.png

 三、对各按键的编写

双击界面中任意一个Button,来到了MFC_CalculatorDlg,cpp文件里面。

在该文件下,找出一个位置,写出按键处理函数

1.事件处理函数

void CMFCCalculatorDlg::AddToEditExp(UINT IDC_Button)
{
	CString strBtn;
	CString strExp;
	GetDlgItem(IDC_Button)->GetWindowText(strBtn);
	GetDlgItem(IDC_EDIT_EXP)->GetWindowText(strExp);
	SetDlgItemText(IDC_EDIT_EXP, strExp + strBtn);
}

2.非特殊按键

然后将各个按键的ID作为函数参数,实现各个Button,例如数字1,2

//‘1’数字,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButton1()
{
	AddToEditExp(IDC_BUTTON_1);
}

//‘2’数字,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButton2()
{
	AddToEditExp(IDC_BUTTON_2);
}

 以上方法用于左括号,有括号,加减乘除,0—9,绝对值,阶乘,次方,小数点的编写。

这些按键的完整代码如下

// 输入区显示按钮操作
void CMFCCalculatorDlg::AddToEditExp(UINT IDC_Button)
{
	CString strBtn;
	CString strExp;
	GetDlgItem(IDC_Button)->GetWindowText(strBtn);
	GetDlgItem(IDC_EDIT_EXP)->GetWindowText(strExp);
	SetDlgItemText(IDC_EDIT_EXP, strExp + strBtn);
}


// ‘(’左括号,按钮事件响应
void CMFCCalculatorDlg::OnBnClickedButtonLbracket()
{
	AddToEditExp(IDC_BUTTON_LBRACKET);
}


// ‘)’右括号,按钮事件响应
void CMFCCalculatorDlg::OnBnClickedButtonRbracket()
{
	AddToEditExp(IDC_BUTTON_RBRACKET);
}


//‘||’绝对值,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButtonAbs()
{
	AddToEditExp(IDC_BUTTON_ABS);
}


//‘!’阶乘,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButtonFac()
{
	AddToEditExp(IDC_BUTTON_FAC);
}


//‘^’幂次方,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButtonPow()
{
	AddToEditExp(IDC_BUTTON_POW);
}


//‘/’除法,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButtonDiv()
{
	AddToEditExp(IDC_BUTTON_DIV);
}


//‘*’乘法,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButtonMul()
{
	AddToEditExp(IDC_BUTTON_MUL);
}


//‘-’减法,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButtonSub()
{
	AddToEditExp(IDC_BUTTON_SUB);
}

//‘+’加法,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButtonAdd()
{
	AddToEditExp(IDC_BUTTON_ADD);
}
//‘.’小数点,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButtonDot()
{
	AddToEditExp(IDC_BUTTON_DOT);
}

//‘0’数字,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButton0()
{
	AddToEditExp(IDC_BUTTON_0);
}

//‘1’数字,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButton1()
{
	AddToEditExp(IDC_BUTTON_1);
}

//‘2’数字,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButton2()
{
	AddToEditExp(IDC_BUTTON_2);
}

//‘3’数字,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButton3()
{
	AddToEditExp(IDC_BUTTON_3);
}

//‘4’数字,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButton4()
{
	AddToEditExp(IDC_BUTTON_4);
}

//‘5’数字,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButton5()
{
	AddToEditExp(IDC_BUTTON_5);
}

//‘6’数字,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButton6()
{
	AddToEditExp(IDC_BUTTON_6);
}

//‘7’数字,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButton7()
{
	AddToEditExp(IDC_BUTTON_7);
}

//‘8’数字,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButton8()
{
	AddToEditExp(IDC_BUTTON_8);
}

//‘9’数字,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButton9()
{
	AddToEditExp(IDC_BUTTON_9);
}

3.特殊按键

(1)清零按键

按键实现代码

void CMFCCalculatorDlg::OnBnClickedButtonClear()
{
	SetDlgItemText(IDC_EDIT_EXP, NULL);
	CString cstr;
	cstr = "0";
	SetDlgItemText(IDC_STATIC_RESULT, cstr);
}

(2)删除按键

void CMFCCalculatorDlg::OnBnClickedButtonDelete()
{
	CString strExp;
	GetDlgItem(IDC_EDIT_EXP)->GetWindowText(strExp);
	strExp = strExp.Left(strExp.GetLength() - 1);
	SetDlgItemText(IDC_EDIT_EXP, strExp);
}

(3)倒数运算

由于倒数运算的特殊性,倒数不参与复合运算。所以可以在倒数按钮函数的上方定义字符串op、double类型数字num1和旗帜变量flag,然后进入函数,利用GetDlgItemText函数获取Eidt Control中的信息,并保存在字符串cs中,令num1等于_ttof(cs),op等于“1/x”,并将flag置为1。

具体代码如下:

double num1;
double result;
CString op;
int flag = 0;
//倒数,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButtonRem()
{
	CString cs;
	GetDlgItemText(IDC_EDIT_EXP, cs);

	num1 = _ttof(cs);

	op = "1/x";
	flag = 1;
}

(4)等于号按键

等于号按键含有计算结果,包含运算过程中各个函数,较为复杂。

首先在等于号对应函数上方定义一个double类型的结果变量result,进入函数后首先判断flag是否等于1,若等于,则进行倒数运算,否则进行其他运算。代码如下

double result;
//‘=’等于,按钮事件处理
void CMFCCalculatorDlg::OnBnClickedButtonEqual()
{
	if (flag == 1) {
		
		CString cs;
		GetDlgItemText(IDC_EDIT_EXP, cs);
		if (num1 != 0) {
			result = 1 / num1;
		}
		
		cs.Format(_T("%g"), result);
		SetDlgItemText(IDC_STATIC_RESULT, cs);
		flag = 0;
	}

	else {
		CString strExp;
		Calculator cal;		//计算类
		CString cstr_Result;
		CString cstr_ErrorInfo;

		GetDlgItem(IDC_EDIT_EXP)->GetWindowText(strExp);
		string infix(CW2A(strExp.GetString()));
		cal.calculate(infix);
		cstr_Result.Format(_T("%g"), cal.getResult());
		cstr_ErrorInfo + cal.getErrorImfo().c_str();
		if (!cstr_ErrorInfo.IsEmpty()) {
			SetDlgItemText(IDC_STATIC_RESULT, cstr_ErrorInfo);
		}
		SetDlgItemText(IDC_STATIC_RESULT, cstr_Result);
	}
}

可以看出,等于号中包含很多运算函数,接下来一一实现他们。

四、运算函数的实现

1.Calculate.cpp文件

Calculate.cpp文件主要对算术符号的优先权等级,初始化Calculator()函数,表达式自定义标准格式化,获取算术符号优先级,以及计算方法,获取结果等。

代码如下

#include "pch.h"
#include "Calculator.h"

#include <stack>
#include <vector>
#include <string>
#include <cmath>
#include <iostream>


using namespace std;


//算术符号优先权等级
enum PRIO_LV {
	PRIO_LV0 = 0,
	PRIO_LV1 = 1,
	PRIO_LV2 = 2,
	PRIO_LV3 = 3,
	PRIO_LV4 = 4,
};


Calculator::Calculator() {				//构造函数,初始化成员变量

	result = 0.0;
	//cal_ErrorImfo = "";
}


//表达式自定义标准格式化
void Calculator::getFormat(string infix) {

	stdInfix = infix;

	//实现正负数
	//for (int i = 0; i < stdInfix.length(); i++) {					//string下标调用运算符时可能会导致类型溢出
	for (size_t i = 0; i < stdInfix.size(); i++) {					//string.size()返回size_type类型,避免下标运算时的类型溢出
		if (stdInfix[i] == '-' || stdInfix[i] == '+') {				//-x转换为0-x,+x转化为0+x
			if (i == 0) {
				stdInfix.insert(0, 1, '0');
			}
			else if (stdInfix[i - 1] == '(') {
				stdInfix.insert(i, 1, '0');
			}
		}
	}
}

//获取算术符号优先级
int Calculator::getPrior(char c) {

	if (c == '+' || c == '-') {
		return PRIO_LV1;
	}
	else if (c == '*' || c == '/') {
		return PRIO_LV2;
	}
	else if (c == '%' || c == '^') {
		return PRIO_LV3;
	}
	else if (c == '!') {
		return PRIO_LV4;
	}
	else {
		return PRIO_LV0;
	}
    string str = "非法符号";
    cout << str << endl;

}

//计算方法
void Calculator::calculate(string infix) {
	getFormat(infix);			//表达式自定义标准格式化
	getPostfix();				//后缀表达式转换
	calResult();				//计算结果
}

//获取结果
double Calculator::getResult() {
	return result;
}

2.GetCalResult.cpp文件

GetCalResult.cpp文件主要是获取计算结果。

代码如下:

#define _CRT_SECURE_NO_WARNINGS
#include "pch.h"
#include "Calculator.h"

#include <stack>
#include <vector>
#include <string>
#include <cmath>
#include <iostream>


void Calculator::calResult() {

	string tmp;
	double number = 0;
	double op1 = 0, op2 = 0;

	for (int i = 0; i < postfix.size(); i++) {
		tmp = postfix[i];
		if (tmp[0] >= '0' && tmp[0] <= '9') {
			number = atof(tmp.c_str());
			figStack.push(number);
		}
		else if (postfix[i] == "+") {
			if (!figStack.empty()) {
				op2 = figStack.top();
				figStack.pop();
			}
			if (!figStack.empty()) {
				op1 = figStack.top();
				figStack.pop();
			}
			figStack.push(op1 + op2);
		}
		else if (postfix[i] == "-") {
			if (!figStack.empty()) {
				op2 = figStack.top();
				figStack.pop();
			}
			if (!figStack.empty()) {
				op1 = figStack.top();
				figStack.pop();
			}
			figStack.push(op1 - op2);
		}
		else if (postfix[i] == "*") {
			if (!figStack.empty()) {
				op2 = figStack.top();
				figStack.pop();
			}
			if (!figStack.empty()) {
				op1 = figStack.top();
				figStack.pop();
			}
			figStack.push(op1 * op2);
		}
		else if (postfix[i] == "/") {
			if (!figStack.empty()) {
				op2 = figStack.top();
				figStack.pop();
			}
			if (!figStack.empty()) {
				op1 = figStack.top();
				figStack.pop();
			}
			if (op2 != 0) {
				///除数不为0,未做处理,默认
			}
			figStack.push(op1 / op2);
		}
		else if (postfix[i] == "^") {
			if (!figStack.empty()) {
				op2 = figStack.top();
				figStack.pop();
			}
			if (!figStack.empty()) {
				op1 = figStack.top();
				figStack.pop();
			}
			figStack.push(pow(op1, op2));
		}
		else if (postfix[i] == "|") {
			if (!figStack.empty()) {
				op1 = figStack.top();
				figStack.pop();
			}
			figStack.push(abs(op1));
		}
		else if (postfix[i] == "!") {
			if (!figStack.empty()) {
				op1 = figStack.top();
				figStack.pop();
			}
			if (op1 > 0) {
				//阶乘数应大于;为小数时(转化为整数求阶)
				double factorial = 1;
				for (int i = 1; i <= op1; ++i)
				{
					factorial *= i;
				}
				op1 = factorial;
			}
			figStack.push(op1);
		}
	}//end for
	if (!figStack.empty()) {
		result = figStack.top();
	}
}

3.transform.cpp文件

  transform.cpp文件主要是将中缀表达式转化为后缀表达式,该文件也是整个工程最为重要最为复杂的一部分,这里我们着重介绍。

  对于一般的,在数学上的,所有的计算表达式均是中缀表达式(这体现在运算符号一般在数字的中间)如对于表达式2+3,其中的加号在数字2,3的中间。将其转化为后缀表达式就是2 3 + ,体现在加号在两个运算数的后面。

  计算机是执行命令的机器,对于中缀表达式,当表达式中有括号,乘法,加法等的复合运算,计算机就需要多次遍历表达式,从而先计算括号等优先级较高的运算符,因此将会大大降低运算效率,所以人们发明出后缀表达式,只需从左向右遍历一遍即可,契合计算机,提高了运算效率。

  计算机从左向右遍历表达式,遇到数字进栈,遇到运算符就退出栈中两个数字,先退出的在运算符前面,后退出在运算符后面,进行计算,然后将运算结果压入栈中,继续遍历表达式,最终栈中就有一个数字,就为运算结果。

代码如下:

#include "pch.h"
#include "Calculator.h"

#include <stack>
#include <vector>
#include <string>
#include <cmath>
#include <iostream>


//绝对值符号个数的奇偶性
enum ABS_ODEVITY {
	ABS_ODD = 1,
	ABS_EVEN = 2,
};
void Calculator::getPostfix() {

	int absNumeber = ABS_ODD;				//绝对值符号个数的奇偶性
	string tmp;

	//for (int i = 0; i < stdInfix.length(); i++) {
	for (size_t i = 0; i < stdInfix.size(); i++) {					//string.size()返回size_type类型,避免下标运算时的类型溢出
		tmp = "";
		switch (stdInfix[i]) {
		case '+':
		case '-':
		case '*':
		case '/':
		case '^':
		case '!':
			if (symStack.empty() || symStack.top() == '(' || symStack.top() == '[' || symStack.top() == '{' || (symStack.top() == '|' && absNumeber == ABS_ODD)) {
				symStack.push(stdInfix[i]);
			}
			else {
				while (!symStack.empty() && (getPrior(symStack.top()) >= getPrior(stdInfix[i]))) {
					tmp += symStack.top();
					postfix.push_back(tmp);
					symStack.pop();
					tmp = "";
				}
				symStack.push(stdInfix[i]);
			}
			break;
		case '|':
			if (absNumeber == ABS_ODD) {
				symStack.push(stdInfix[i]);
				absNumeber = ABS_EVEN;
			}
			else {
				while (!symStack.empty() && symStack.top() != '|') {
					tmp += symStack.top();
					postfix.push_back(tmp);
					symStack.pop();
					tmp = "";
				}
				if (!symStack.empty() && symStack.top() == '|') {
					tmp += symStack.top();
					postfix.push_back(tmp);						//左绝对值符号'|'加入后缀表达式,用于绝对值的检测计算
					symStack.pop();
					absNumeber = ABS_ODD;
				}
			}
			break;
		case '(':
		case '[':
		case '{':
			symStack.push(stdInfix[i]);
			break;
		case ')':
			while (!symStack.empty() && symStack.top() != '(') {
				tmp += symStack.top();
				postfix.push_back(tmp);
				symStack.pop();
				tmp = "";
			}
			if (!symStack.empty() && symStack.top() == '(') {
				symStack.pop();							//将左括号出栈丢弃
			}
			break;
		case ']':
			while (!symStack.empty() && symStack.top() != '[') {
				tmp += symStack.top();
				postfix.push_back(tmp);
				symStack.pop();
				tmp = "";
			}
			if (!symStack.empty() && symStack.top() == '[') {
				symStack.pop();							//将左括号出栈丢弃
			}
			break;
		case '}':
			while (!symStack.empty() && symStack.top() != '{') {
				tmp += symStack.top();
				postfix.push_back(tmp);
				symStack.pop();
				tmp = "";
			}
			if (!symStack.empty() && symStack.top() == '{') {
				symStack.pop();							//将左括号出栈丢弃
			}
			break;
		default:
			if ((stdInfix[i] >= '0' && stdInfix[i] <= '9')) {
				tmp += stdInfix[i];
				while (i + 1 < stdInfix.length() && (stdInfix[i + 1] >= '0' && stdInfix[i + 1] <= '9' || stdInfix[i + 1] == '.')) {		//小数处理

					tmp += stdInfix[i + 1];			//是连续的数字,则追加
					i++;
				}
				if (tmp[tmp.length() - 1] == '.') {
					tmp += '0';						//将x.做x.0处理
				}
				postfix.push_back(tmp);
			}
			break;
		}
	}

	//if(!symStack.empty()) {
	while (!symStack.empty()) {						//将栈中剩余符号加入后缀表达式
		tmp = "";
		tmp += symStack.top();
		postfix.push_back(tmp);
		symStack.pop();
	}
}

4.Calculator.h  文件(头文件)

代码如下:

#pragma once

#include <stack>
#include <vector>
#include <string>
using namespace std;

//计算器类
class Calculator
{
public:
	Calculator();
	void calculate(string infix);		//计算方法
	void getFormat(string infix);		//表达式自定义标准格式化
	int getPrior(char c);				//获取算术符号优先级
	void getPostfix();					//后缀表达式转换
	void calResult();					//计算结果
	double getResult();					//获取结果
	string operatorSym;					//运算符号

private:
	vector<string> postfix;				//后缀表达式向量
	stack<char> symStack;				//符号栈
	stack<double> figStack;				//数字栈
	string stdInfix;					//自定义标准格式化表达式
	double result;						//最终计算结果
};

五、计算示例

5ecb030c17e54d1db4afbf195973d5e7.png

普通的复合运算

83f055dbd6954ef4874a8d75e85d1277.png

绝对值,倒数,次方的运算

301c9561c4244c92aabff959ca3007ea.pngd2d6fabfc74a418d816a720549efbeef.png

 

 3b03bef403f04d6e8c4eb540286c5c52.png

 小数的运算

c79ce04d5c3d4edd8abac6969c33609a.png

 

 

 

 

 

 

  • 61
    点赞
  • 351
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

噜噜噜噜鲁先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值