基于MFC的一个简单计算器
写一个简单的计算器并不是什么很难的事,主要目的是要通过这个程序来学习和分析其中的核心算法。这个简易计算器的核心部分就是对输入的表达式的正确性判断与求值,其中包括对表达式的解析、中缀表达式转后缀表达式、后缀表达式求值等等几个方面。
一、封装核心代码
《算术表达式的合法性判断与求值(上)》和《算术表达式的合法性判断与求值(下)》这两篇文章已经对核心算法部分进行了讲解,并附有源码。在此基础上制作一个简单计算器,我们要做的仅仅是封装核心代码并加入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的一个简单计算器的更多相关文章
- 基于PHP实现一个简单的在线聊天功能(轮询ajax )
基于PHP实现一个简单的在线聊天功能(轮询ajax ) 一.总结 1.用的轮询ajax 二.基于PHP实现一个简单的在线聊天功能 一直很想试着做一做这个有意思的功能,感觉复杂的不是数据交互和表结构,麻 ...
- 基于 Roslyn 实现一个简单的条件解析引擎
基于 Roslyn 实现一个简单的条件解析引擎 Intro 最近在做一个勋章的服务,我们想定义一些勋章的获取条件,满足条件之后就给用户颁发一个勋章,定义条件的时候会定义需要哪些参数,参数的类型,获取勋 ...
- 使用CEF(二)— 基于VS2019编写一个简单CEF样例
使用CEF(二)- 基于VS2019编写一个简单CEF样例 在这一节中,本人将会在Windows下使用VS2019创建一个空白的C++Windows Desktop Application项目,逐步进 ...
- [MFC] 梳理一个简单的图片处理桌面软件中用到的MFC控件技巧
前言 前些天应好友之拖,帮忙设计一个简单的图像处理的小软件.朋友把核心算法封装好了,但是是用openCV类似于console的编程环境,要我在此基础上改成MFC桌面程序.下图是做成之后的效果: 我是 ...
- 用MFC完成一个简单的猜数字游戏: 输入的四位数中,位置和数字都正确为A,数字相同而位置不同的为B。
最近学习了MFC一些比较基础的知识,所以打算通过做一个简单的数字游戏来理解MFC的流程并进一步熟悉其操作. 在这里,我做了一个猜数字的小游戏.第一步当然是设计主界面,先给大家展示一下游戏界面: 主界面 ...
- 基于SOUI开发一个简单的小工具
基于DriectUI有很多库,比如 Duilib (免费) soui (免费) DuiVision (免费) 炫彩 (界面库免费,UI设计器付费,不提供源码) skinui (免费使用,但不开放源码, ...
- 基于node实现一个简单的脚手架工具(node控制台交互项目)
实现控制台输入输出 实现文件读写操作 全原生实现一个简单的脚手架工具 实现vue-cli2源码 一.实现控制台输入输出 关于控制台的输入输出依然是基于node进程管理对象process,在proces ...
- 【玩转开源】BananaPi R2 —— 第三篇 基于Openwrt开发一个简单的路由器
上一篇讲解了R2的网口配置,这一篇我们以BananaPi R2为例子来实现一个简单的路由器:那么一个简单的路由器应该具备什么样的功能呢?最简单的说就是wan+lan+ap这三个功能. 首先wan+la ...
- 通过MFC设计一个简单的计价程序
1.实验目的 掌握使用MFC应用程序向导创建应用程序的方法. 掌握新建对话框资源的方法. 掌握生成对话框的方法. 2.实验内容 用应用程序创建一个默认的对话框应用程序,在对话框中能进入下一个对话框,在 ...
随机推荐
- Hibernate的入门使用
数据在各个层次之间流转,在流转过程中会发生数据类型转换等一系列的交互问题.java与html的交互解决方案是struts2提供的OGNL(对象导航图语言),而java与数据库之间的技术解决方案是ORM ...
- Java锁,真的有这么复杂吗?
为什么使用synchronizedvolatile,在多线程下可以保证变量的可见性,但是不能保证原子性,下面一段代码说明: 运行上面代码,会发现输出flag的值不是理想中10000,虽然volatil ...
- [CodeForces]1059D Nature Reserve
大意:给你一个平面上N(N<=100000)个点,问相切于x轴的圆,将所有的点都覆盖的最小半径是多少. 计算几何???Div2的D题就考计算几何???某人昨天上课才和我们说这种计算几何题看见就溜 ...
- bash之数组
Bash 提供索引和联想的一维数组变量.可用作任何变量 索引的数组 :内置的声明将显式声明数组.有没有最大限制 一个数组,也没有要求成员将索引或连续分配的大小.索引的数组 引用使用 (包括算术表达式) ...
- genymotion 双击打开后 图标只显示在任务栏 无法在电脑上显示
解决办法 删除 c:/users/user/AppData/local/Genymobile 例如:C:\Users\lenovo\AppData\Local\Genymobile 删除注册表:HK ...
- qrcode.js
(function(r){r.fn.qrcode=function(h){var s;function u(a){this.mode=s;this.data=a}function o(a,c){thi ...
- 笔试算法题(40):后缀数组 & 后缀树(Suffix Array & Suffix Tree)
议题:后缀数组(Suffix Array) 分析: 后缀树和后缀数组都是处理字符串的有效工具,前者较为常见,但后者更容易编程实现,空间耗用更少:后缀数组可用于解决最长公共子串问题,多模式匹配问题,最长 ...
- [BZOJ] 1037 [ZJOI2008]生日聚会
Time Limit: 10 Sec Memory Limit: 162 MB Submit: 3007 Solved: 1795 [Submit][Status][Discuss] Descript ...
- Python之爬虫-中国大学排名
Python之爬虫-中国大学排名 #!/usr/bin/env python # coding: utf-8 import bs4 import requests from bs4 import Be ...
- (八)python3 迭代
迭代:如果给定一个 list 或 tuple,我们可以通过 for 循环来遍历这个 list 或tuple,这种遍历我们称为迭代(Iteration) 字典: >>> d = {'a ...