基于MFC的一个简单计算器

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




一、封装核心代码

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

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

Expression.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
#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;
}

使用方法(测试):

1
2
3
4
5
6
7
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,然后给各个按钮添加单击响应函数,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
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