写一个简单的计算器并不是什么很难的事,主要目的是要通过这个程序来学习和分析其中的核心算法。这个简易计算器的核心部分就是对输入的表达式的正确性判断与求值,其中包括对表达式的解析、中缀表达式转后缀表达式、后缀表达式求值等等几个方面。

一、封装核心代码

算术表达式的合法性判断与求值(上)》和《算术表达式的合法性判断与求值(下)》这两篇文章已经对核心算法部分进行了讲解,并附有源码。在此基础上制作一个简单计算器,我们要做的仅仅是封装核心代码并加入MFC工程中。

下面是我封装的一个 Expression 类:

Expression.h

#pragma once
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <stack>
#include <utility>
using namespace std; class Expression
{
public:
Expression(string str);
bool test(); // 外部接口,判断表达式是否合法
double calculate(); // 外部接口,计算表达式的值 private:
vector<pair<string, int>> word;
string expr; // 算术表达式
int idx; // word下标
int sym; // 单词种别编码
int err; // 错误
int word_analysis(vector<pair<string, int>>& , const string);
void Next();
void E();
void T();
void F();
bool Right; // 保存表达式test结果 private:
int prior(int); // 获取运算符的优先级
bool isOperator(int); // 通过 种别编码 判定是否是运算符
vector<pair<string,int>> getPostfix(const vector<pair<string,int>>&); // 中缀转后缀
void popTwoNumbers(stack<double>&, double&, double&); // 从栈中连续弹出两个操作数
double stringToDouble(const string&); // 把string转换为double
double expCalculate(const vector<pair<string, int>>&); // 计算后缀表达式的值
};

Expression.cpp

#include "Expression.h"

// 构造函数
Expression::Expression( string str ):
expr(str),
idx(0),
err(0),
Right(true)
{ } // 外部接口
bool Expression::test()
{
if(!word.empty()) // 已经test过了
{
return Right;
} int err_num = word_analysis(word, expr);
if (-1 == err_num)
{
Right = false;
}
else
{
// 词法正确,进行语法分析
Next();
E();
if (sym == 0 && err == 0) // 注意要判断两个条件
Right = true;
else
Right = false;
}
return Right;
} // 外部接口
double Expression::calculate()
{
if (test())
{
return expCalculate(getPostfix(word));
}
else
{
exit(0);
}
} /*--------------------------------词法分析----------------------------*/
int Expression::word_analysis(vector<pair<string, int>>& word, const string expr)
{
for(int i=0; i<expr.length(); ++i)
{
// 如果是 + - x ÷ ( )
if(expr[i] == '(' || expr[i] == ')' || expr[i] == '+'
|| expr[i] == '-' || expr[i] == '*' || expr[i] == '/')
{
string tmp;
tmp.push_back(expr[i]);
switch (expr[i])
{
case '+':
word.push_back(make_pair(tmp, 1));
break;
case '-':
word.push_back(make_pair(tmp, 2));
break;
case '*':
word.push_back(make_pair(tmp, 3));
break;
case '/':
word.push_back(make_pair(tmp, 4));
break;
case '(':
word.push_back(make_pair(tmp, 6));
break;
case ')':
word.push_back(make_pair(tmp, 7));
break;
}
}
// 如果是数字开头
else if(expr[i]>='0' && expr[i]<='9')
{
string tmp;
while(expr[i]>='0' && expr[i]<='9')
{
tmp.push_back(expr[i]);
++i;
}
if(expr[i] == '.')
{
++i;
if(expr[i]>='0' && expr[i]<='9')
{
tmp.push_back('.');
while(expr[i]>='0' && expr[i]<='9')
{
tmp.push_back(expr[i]);
++i;
}
}
else
{
return -1; // .后面不是数字,词法错误
}
}
word.push_back(make_pair(tmp, 5));
--i;
}
// 如果以.开头
else
{
return -1; // 以.开头,词法错误
}
}
return 0;
} /*--------------------------------语法分析----------------------------*/
// 读下一单词的种别编码
void Expression::Next()
{
if(idx < word.size())
sym = word[idx++].second;
else
sym = 0;
} // E → T { +T | -T }
void Expression::E()
{
T();
while(sym == 1 || sym == 2)
{
Next();
T();
}
} // T → F { *F | /F }
void Expression::T()
{
F();
while(sym == 3 || sym == 4)
{
Next();
F();
}
} // F → (E) | d
void Expression::F()
{
if (sym == 5)
{
Next();
}
else if(sym == 6)
{
Next();
E();
if (sym == 7)
{
Next();
}
else
{
err = -1;
}
}
else
{
err = -1;
}
} /*--------------------------------求值部分----------------------------*/
int Expression::prior(int sym)
{
switch (sym)
{
case 1:
case 2:
return 1;
case 3:
case 4:
return 2;
default:
return 0;
}
} bool Expression::isOperator(int sym)
{
switch (sym)
{
case 1:
case 2:
case 3:
case 4:
return true;
default:
return false;
}
} vector<pair<string,int>> Expression::getPostfix( const vector<pair<string,int>>& expr)
{
vector<pair<string, int>> output; // 输出
stack<pair<string, int>> s; // 操作符栈
for(int i=0; i<expr.size(); ++i)
{
pair<string, int> p = expr[i];
if(isOperator(p.second))
{
while(!s.empty() && isOperator(s.top().second) && prior(s.top().second)>=prior(p.second))
{
output.push_back(s.top());
s.pop();
}
s.push(p);
}
else if(p.second == 6)
{
s.push(p);
}
else if(p.second == 7)
{
while(s.top().second != 6)
{
output.push_back(s.top());
s.pop();
}
s.pop();
}
else
{
output.push_back(p);
}
}
while (!s.empty())
{
output.push_back(s.top());
s.pop();
}
return output;
} void Expression::popTwoNumbers( stack<double>& s, double& first, double& second )
{
first = s.top();
s.pop();
second = s.top();
s.pop();
} double Expression::stringToDouble( const string& str )
{
double d;
stringstream ss;
ss << str;
ss >> d;
return d;
} double Expression::expCalculate( const vector<pair<string,int>>& postfix )
{
double first,second;
stack<double> s;
for(int i=0; i<postfix.size(); ++i)
{
pair<string,int> p = postfix[i];
switch (p.second)
{
case 1:
popTwoNumbers(s, first, second);
s.push(second+first);
break;
case 2:
popTwoNumbers(s, first, second);
s.push(second-first);
break;
case 3:
popTwoNumbers(s, first, second);
s.push(second*first);
break;
case 4:
popTwoNumbers(s, first, second);
s.push(second/first);
break;
default:
s.push(stringToDouble(p.first));
break;
}
}
double result = s.top();
s.pop();
return result;
}

使用方法(测试):

int main()
{
Expression e("(1.5+2.5)*2+0.53");
if(e.test()) // 判断表达式是否合法
cout << e.calculate() << endl;
return 0;
}

二、加入MFC工程

OK,核心代码(表达式的合法性判断与求值)已经封装到 Expression 类中,下面要做的就是新建一个 MFC 工程,并把 Expression 类加入工程里,并实现按钮的功能就行了。

在 MFC 对话框上添加二十个 Button 控件和一个 Edit 控件(设置Disable属性为true,只用于显示),如下图:

给 Edit 控件绑定一个变量 mEdit,然后给各个按钮添加单击响应函数,代码如下:

void CCalculatorDlg::OnBnClicked1()
{
// 数字“1”按钮
CString str;
mEdit.GetWindowText(str);
str = str + _T("1");
mEdit.SetWindowText(str);
} void CCalculatorDlg::OnBnClicked2()
{
// 数字“2”按钮
CString str;
mEdit.GetWindowText(str);
str = str + _T("2");
mEdit.SetWindowText(str);
} void CCalculatorDlg::OnBnClicked3()
{
// 数字“3”按钮
CString str;
mEdit.GetWindowText(str);
str = str + _T("3");
mEdit.SetWindowText(str);
} void CCalculatorDlg::OnBnClicked4()
{
// 数字“4”按钮
CString str;
mEdit.GetWindowText(str);
str = str + _T("4");
mEdit.SetWindowText(str);
} void CCalculatorDlg::OnBnClicked5()
{
// 数字“5”按钮
CString str;
mEdit.GetWindowText(str);
str = str + _T("5");
mEdit.SetWindowText(str);
} void CCalculatorDlg::OnBnClicked6()
{
// 数字“6”按钮
CString str;
mEdit.GetWindowText(str);
str = str + _T("6");
mEdit.SetWindowText(str);
} void CCalculatorDlg::OnBnClicked7()
{
// 数字“7”按钮
CString str;
mEdit.GetWindowText(str);
str = str + _T("7");
mEdit.SetWindowText(str);
} void CCalculatorDlg::OnBnClicked8()
{
// 数字“8”按钮
CString str;
mEdit.GetWindowText(str);
str = str + _T("8");
mEdit.SetWindowText(str);
} void CCalculatorDlg::OnBnClicked9()
{
// 数字“9”按钮
CString str;
mEdit.GetWindowText(str);
str = str + _T("9");
mEdit.SetWindowText(str);
} void CCalculatorDlg::OnBnClicked0()
{
// 数字“0”按钮
CString str;
mEdit.GetWindowText(str);
str = str + _T("0");
mEdit.SetWindowText(str);
} void CCalculatorDlg::OnBnClickedClear()
{
// “清屏”按钮
mEdit.SetWindowText(_T(""));
} void CCalculatorDlg::OnBnClickedBack()
{
// “后退”按钮
CString str;
mEdit.GetWindowText(str);
str = str.Left(str.GetLength()-1);
mEdit.SetWindowText(str);
} void CCalculatorDlg::OnBnClickedLeft()
{
// “左括号”按钮
CString str;
mEdit.GetWindowText(str);
str = str + _T("(");
mEdit.SetWindowText(str);
} void CCalculatorDlg::OnBnClickedRight()
{
// “右括号”按钮
CString str;
mEdit.GetWindowText(str);
str = str + _T(")");
mEdit.SetWindowText(str);
} void CCalculatorDlg::OnBnClickedDot()
{
// "."按钮
CString str;
mEdit.GetWindowText(str);
str = str + _T(".");
mEdit.SetWindowText(str);
} void CCalculatorDlg::OnBnClickedAdd()
{
// 加号
CString str;
mEdit.GetWindowText(str);
str = str + _T("+");
mEdit.SetWindowText(str);
} void CCalculatorDlg::OnBnClickedSub()
{
// 减号
CString str;
mEdit.GetWindowText(str);
str = str + _T("-");
mEdit.SetWindowText(str);
} void CCalculatorDlg::OnBnClickedMul()
{
// 乘号
CString str;
mEdit.GetWindowText(str);
str = str + _T("*");
mEdit.SetWindowText(str);
} void CCalculatorDlg::OnBnClickedDiv()
{
// 除号
CString str;
mEdit.GetWindowText(str);
str = str + _T("/");
mEdit.SetWindowText(str);
} void CCalculatorDlg::OnBnClickedEql()  
{  
    // 等号,计算结果  
    CString str;  
    mEdit.GetWindowText(str);  
    CT2CA pszConvertedAnsiString(str);  // 将 TCHAR 转换为 LPCSTR  
    string exp_str(pszConvertedAnsiString); // 从 LPCSTR 构造 string  
 
    if (exp_str != "")
    {
        Expression e(exp_str);  
        if(e.test())  
        {  
            string tmp;  
            stringstream ss;  
            ss << e.calculate();  
            ss >> tmp;  
            str = tmp.c_str();  
        }  
        else  
        {  
            str = "输入错误";  
        }  
        mEdit.SetWindowText(str);
    }
}

这样,一个可以计算整数和小数的四则混合运算的简单计算器就完成了。

虽然 Expression 类可以对所有的输入进行解析,并判断其合法性。但是考虑到用户体验,最好还是对各个 Button 的输入进行一定的限制,比如+++++++。这就需要在按钮的响应函数里添加逻辑,详细的就不多说了。

源码下载:http://download.csdn.net/detail/lisong694767315/8009467

基于MFC的一个简单计算器的更多相关文章

  1. 基于PHP实现一个简单的在线聊天功能(轮询ajax )

    基于PHP实现一个简单的在线聊天功能(轮询ajax ) 一.总结 1.用的轮询ajax 二.基于PHP实现一个简单的在线聊天功能 一直很想试着做一做这个有意思的功能,感觉复杂的不是数据交互和表结构,麻 ...

  2. 基于 Roslyn 实现一个简单的条件解析引擎

    基于 Roslyn 实现一个简单的条件解析引擎 Intro 最近在做一个勋章的服务,我们想定义一些勋章的获取条件,满足条件之后就给用户颁发一个勋章,定义条件的时候会定义需要哪些参数,参数的类型,获取勋 ...

  3. 使用CEF(二)— 基于VS2019编写一个简单CEF样例

    使用CEF(二)- 基于VS2019编写一个简单CEF样例 在这一节中,本人将会在Windows下使用VS2019创建一个空白的C++Windows Desktop Application项目,逐步进 ...

  4. [MFC] 梳理一个简单的图片处理桌面软件中用到的MFC控件技巧

     前言 前些天应好友之拖,帮忙设计一个简单的图像处理的小软件.朋友把核心算法封装好了,但是是用openCV类似于console的编程环境,要我在此基础上改成MFC桌面程序.下图是做成之后的效果: 我是 ...

  5. 用MFC完成一个简单的猜数字游戏: 输入的四位数中,位置和数字都正确为A,数字相同而位置不同的为B。

    最近学习了MFC一些比较基础的知识,所以打算通过做一个简单的数字游戏来理解MFC的流程并进一步熟悉其操作. 在这里,我做了一个猜数字的小游戏.第一步当然是设计主界面,先给大家展示一下游戏界面: 主界面 ...

  6. 基于SOUI开发一个简单的小工具

    基于DriectUI有很多库,比如 Duilib (免费) soui (免费) DuiVision (免费) 炫彩 (界面库免费,UI设计器付费,不提供源码) skinui (免费使用,但不开放源码, ...

  7. 基于node实现一个简单的脚手架工具(node控制台交互项目)

    实现控制台输入输出 实现文件读写操作 全原生实现一个简单的脚手架工具 实现vue-cli2源码 一.实现控制台输入输出 关于控制台的输入输出依然是基于node进程管理对象process,在proces ...

  8. 【玩转开源】BananaPi R2 —— 第三篇 基于Openwrt开发一个简单的路由器

    上一篇讲解了R2的网口配置,这一篇我们以BananaPi R2为例子来实现一个简单的路由器:那么一个简单的路由器应该具备什么样的功能呢?最简单的说就是wan+lan+ap这三个功能. 首先wan+la ...

  9. 通过MFC设计一个简单的计价程序

    1.实验目的 掌握使用MFC应用程序向导创建应用程序的方法. 掌握新建对话框资源的方法. 掌握生成对话框的方法. 2.实验内容 用应用程序创建一个默认的对话框应用程序,在对话框中能进入下一个对话框,在 ...

随机推荐

  1. CAD交互绘制文字(com接口)

    在cad设计时,需要绘制文字,用户可以设置设置绘制文字的高度等属性. 主要用到函数说明: _DMxDrawX::DrawText 绘制一个单行文字.详细说明如下: 参数 说明 DOUBLE dPosX ...

  2. php基础排序算法

    1.冒泡排序 $arr = array(12,34,57,42,165.4,73,51); function bubbling_sort($array) { $cou = count($array); ...

  3. 第2节 mapreduce深入学习:8、手机流量汇总求和

    第2节 mapreduce深入学习:8.手机流量汇总求和 例子:MapReduce综合练习之上网流量统计. 数据格式参见资料夹 需求一:统计求和 统计每个手机号的上行流量总和,下行流量总和,上行总流量 ...

  4. Spring Data Redis入门示例:Hash操作(七)

    将对象存为Redis中的hash类型,可以有两种方式,将每个对象实例作为一个hash进行存储,则实例的每个属性作为hash的field:同种类型的对象实例存储为一个hash,每个实例分配一个field ...

  5. 神经网络(NN)+反向传播算法(Backpropagation/BP)+交叉熵+softmax原理分析

    神经网络如何利用反向传播算法进行参数更新,加入交叉熵和softmax又会如何变化? 其中的数学原理分析:请点击这里.

  6. CSU1007: 矩形着色

    Description Danni想为屏幕上的一个矩形着色,但是她想到了一个问题.当点击鼠标以后电脑是如何判断填充的区域呢? 现在给你一个平面直角坐标系,其中有一个矩形和一个点,矩形的四条边均是平行于 ...

  7. 零基础入门学习Python(7)--了不起的分支和循环1

    前言 我们今天的主题,是了不起的分支和循环,为什么不说c语言,Python了不起,而对分支和循环这两个知识点那么崇拜呢? 我们之前的几节课里也接触到了分支和循环,大家思考一下,如果我们的程序没有分支和 ...

  8. python视频 神经网络 Tensorflow

    python视频 神经网络 Tensorflow 模块 视频教程 (带源码) 所属网站分类: 资源下载 > python视频教程 作者:smile 链接:http://www.pythonhei ...

  9. 2017 计蒜之道 初赛 第一场 A 阿里的新游戏

    题链:https://nanti.jisuanke.com/t/15499 这题观察图纸可知成三线段上的相邻点之间的距离有1,2,3三种情况的,同时要成线段必然是同横坐标或者纵坐标,然后我们排除掉穿过 ...

  10. C51 静态数码管 个人笔记

    显示器介绍 单片机系统中常用的显示器有: LED(Light Emitting Diode):发光二极管显示器 LCD(Liquid Crystal Display)液晶显示器 TFT 液晶显示器等. ...