基于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





  • 7
    点赞
  • 109
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值