一、创建VS文件
首先,打开vs,选择如图所示左边的MFC应用。更改项目名称并选择自己想存入的位置后,点击创建。
点击创建后,在应用程序类型中选择“基于对话框”,如下图。
其他名称无需更改。可以在生成的类中更改类名,如下图,我改为MFC_Calculator。
点击完成后来到这个界面。屏幕左侧有“工具箱”。工具箱里面有我们会用到的一些组件。
二、对于计算器界面的创建
我的MFC计算器界面如下,这个也是这篇文章的最终目标。
可以看出,这个计算器具有整数和小数的加、减、乘、除等基本运算,也有倒数、次方、阶乘运算,以及有括号的复合运算,并具有清零(C),删除(Del)等功能。
接下来我们来一一创建。
1.添加Edit Control
在左侧的工具箱找到Edit Control组件,点击添加至我们的界面。
添加后,调整合适的大小。界面右下角的确定、取消按钮,可以单击右键,选择删除。调整完成后,如图
2.添加Static Text
左侧工具箱中选择Static Text,添加至界面,并调整位置和大小。
此时,右键单击刚添加的Static Text,选择最下方的属性,将边框更改为True。如下图。
3.添加Button
将界面中间的“TODO:在此放置对话框控件。”右键单击删除。
左侧工具箱中选择Button,添加至界面。共添加24个Button。可以利用复制粘贴的方式快速添加。
添加后可以在VS的工具栏中找到对齐方式快速对齐
然后一一更改每个Button的属性中的描述文字和ID
数字1 位于界面左下方,所以左下方的Button的描述文字改为“1”,ID我改为“IDC_BUTTON_1”。
更改完成后如下图。
类似的,将其他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
更改完成后,界面如下
三、对各按键的编写
双击界面中任意一个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; //最终计算结果
};
五、计算示例
普通的复合运算
绝对值,倒数,次方的运算
小数的运算
版权归原作者 噜噜噜噜鲁先生 所有, 如有侵权,请联系我们删除。