上篇写了MFC界面搭建,这篇就写实现计算。涉及到数据结构,对新手很不友好。
虽然是MFC程序,但是能灵活地分离后台代码,自行构建控制台程序。
上一篇文章链接:C++做四则运算的MFC计算器(一)MFC界面创建
- 中缀表达式与后缀表达式
- 栈的相关实现
- 用栈将中缀表达式转换成后缀表达式
- 栈计算后缀表达式
- 等号按钮功能-计算结果显示
中缀表达式与后缀表达式
中缀:(60-20)/(5-1)。小学就学的东西
后缀:60 20 – 5 1 - /,为了看上去明显,以“#”做分隔符,即60#20#-#5#1#-#/。
后缀计算:从左到右遇到符号就计算符号左边的两个数并写上新值,即优先的运算符相对在左。上例中遇到第一个‘-’算60-20得40,遇到第二个“-”计算5-1得4,遇到“/”计算30/3的10,结果是10。
对比:中缀式人看起来方便,后缀式没有括号,计算顺序从前到后,计算机操作起来简单。
栈的相关实现
输入的值都是字符,所以需要一个字符结构的栈;在计算数时就需要一个处理数字结构的栈。
字符和数值的栈结构体分别命名SqStack和SqStackN。
然后实现栈初始化、进栈、出栈等栈操作函数。
在MFC程序中,同意把栈结构体和操作函数声明在头文件stdafx.h中,函数体的实现卸载独立的cpp文件中。
1//栈结构体2structSqStack{3chardata[maxsize];4inttop;5};6structSqStackN{7doubledata[maxsizen];8inttop;9};10//栈操作函数—字符11voidinitStack(SqStack*&s);12boolPush(SqStack*&s,chare);13boolPop(SqStack*&s,char&e);14boolGetTop(SqStack*s,char&e);15voidDestroyStack(SqStack*&s);16boolStackEmpty(SqStack*s);17//栈操作函数—数字18voidinitStack(SqStackN*&s);19boolPush(SqStackN*&s,doublee);20boolPop(SqStackN*&s,double&e);21boolGetTop(SqStackN*s,double&e);22voidDestroyStack(SqStackN*&s);23boolStackEmpty(SqStackN*s);
1voidinitStack(SqStack*&s){2s=newSqStack();3s->top=-1;4}5boolPush(SqStack*&s,chare){6if(s->top==maxsize-1)7returnfalse;8s->top++;9s->data[s->top]=e;10returntrue;11}12boolPop(SqStack*&s,char&e){13if(s->top==-1)14returnfalse;15e=s->data[s->top];16s->top--;17returntrue;18}19boolGetTop(SqStack*s,char&e){20if(s->top==-1)21returnfalse;22e=s->data[s->top];23returntrue;24}25voidDestroyStack(SqStack*&s){26free(s);27}28boolStackEmpty(SqStack*s){29return(s->top==-1);30}
用栈将中缀表达式转换成后缀表达式
从左向右扫描中缀,遇到数字就添加到后缀中,遇到运算符进行栈处理,而栈的处理依赖于运算符优先级,优先级高的靠近栈顶。结束后后缀式仍是字符数组。
写个函数tans(),有2个参数char * exp和char postexp[ ],
先初始化一个字符栈指针s,char e用来操作栈顶元素,int p作为postexp数组的下表。写个循环扫描exp
while (*exp != '\0') {switch(*exp){case:case:case:default}......}
扫描到数字字符时直接加到后缀式中,并加上 ‘ # ’ 以分割。
其他情况无非是+、-、*、/、(、)这6个符号。
(、)优先级最高,在括号之间的运算符一定比括号之外的优先运算,遇到 ‘ ( ’ 即进栈,遇 ‘ ) ’ 即出栈栈顶元素直到出来的是 ‘ ( ’ 。因此栈中不会有 ‘ ) ’ 。
*、/优先级其次,先判断栈顶是什么,栈顶是*、/则将其出栈值后缀式,栈顶是+、-则将 ‘ * ’ 或 ‘ / ’ 进栈。
+、-优先级最低,这是栈顶元素不管是+-*/都出栈至后缀式,但栈顶是 ‘ ( ’ 时就不需要出栈,‘ ( ’可以看作是一个新的运算起点,将+或-进栈即可。
exp扫描后栈可能还会有运算符,将剩下的都出栈至后缀式。再为后缀式加结束标识 ‘\0’ ,销毁栈释放空间。
1 void trans(char* exp,char postexp[]) {
2 char e;
3 SqStack *s; initStack(s); // 即SqStack s = new SqStacck()
4 int p = 0;//postexp的下表
5 while (*exp != '\0') {
6 switch (*exp)
7 {
8 case '+':
9 case '-':
10 while (!StackEmpty(s)) {
11 GetTop(s, e);
12 if (e != '(') {
13 postexp[p++] = e;
14 Pop(s, e);
15 }
16 else
17 break;
18 }
19 Push(s, *exp);
20 exp++;break;
21 case '*':
22 case '/':
23 while (!StackEmpty(s)) {
24 GetTop(s, e);
25 if (e == '*' || e == '/') {
26 postexp[p++] = e;
27 Pop(s, e);
28 }
29 else
30 break;
31 }
32 Push(s, *exp);
33 exp++;break;
34 case '(':
35 Push(s, *exp);
36 exp++;break;
37 case ')':
38 Pop(s, e);
39 while (e != '(') {
40 postexp[p++] = e;
41 Pop(s, e);
42 }
43 exp++;break;
44 default:
45 while (*exp >= '0'&&*exp <= '9') {
46 postexp[p++] = *exp;
47 exp++;
48 }
49 postexp[p++] = '#';
50 break;
51 }
52 }
53 while (!StackEmpty(s)) {
54 Pop(s, e);
55 postexp[p++] = e;
56 }
57 postexp[p] = '\0';
58 DestroyStack(s);
59 }
栈计算后缀表达式
这一功能相对简单些,扫描后缀式,
遇到表示数字的字符时,例如 ‘ 6 ’ ,利用它与 ‘ 0 ’ 编码之间的差得到数字,‘ 6 ’ - ‘ 0 ’ = 6 ,乘10实现位数值。得到的数值放入栈里。
遇到运算符时直接出栈两个元素,此时这两个元素一定是数字,第一个出栈的做运算符右值,第二个做左值,顺序不能反,结果还要入栈以进行下一步运算。
遇到 ‘ / ’ 时还要判断栈顶是否是0,被除数可不能是0啊。
最后栈中就是运算结果,出栈作为返回值。
1 double calculate(char* postexp) {
2 double a, b,c,d,e;
3 SqStackN *o; initStack(o);
4 while (*postexp != '\0') {
5 switch (*postexp)
6 {
7 case '+':
8 Pop(o, a);
9 Pop(o, b);
10 c = b + a;
11 Push(o, c);
12 break;
13 case '-':
14 Pop(o, a);
15 Pop(o, b);
16 c = b - a;
17 Push(o, c);
18 break;
19 case '*':
20 Pop(o, a);
21 Pop(o, b);
22 c = b * a;
23 Push(o, c);
24 break;
25 case '/':
26 Pop(o, a);
27 Pop(o, b);
28 if (a != 0) {
29 c = b / a;
30 Push(o, c);
31 break;
32 }
33 else {
34 exit(0);
35 }
36 break;
37 default:
38 d = 0;
39 while (*postexp >= '0'&&*postexp <= '9') {
40 d = d * 10 + (*postexp - '0');
41 postexp++;
42 }
43 Push(o, d);
44 break;
45 }
46 postexp++;
47 }
48 GetTop(o, e);
49 DestroyStack(o);
50 return e;
51 }
等号按钮功能-计算结果显示
运算功能已经实现了,但是要讲功能绑定到 ‘ = ’ 还有最后一道坎~
MFC文本编辑框的类型是CString,要想转换成char[]有点让人头大。
需要几个变量,输入的算式char exp[50],后缀式char postexp[50],运算结果result。
CString转换为char[]直接上代码吧: ::wsprintfA(exp, "%ls", (LPCTSTR)editv);
double转CString代码: resultv.Format(_T("%.5lf"), result);
1 void CMFCcalculationDlg::OnBnClickedButton19()
2 {
3 // TODO: 等号按钮
4 char exp[50];
5 char postexp[50];
6 double result;
7 UpdateData(true);
8 ::wsprintfA(exp, "%ls", (LPCTSTR)editv);
9 trans(exp, postexp);
10 result = calculate(postexp);
11 resultv.Format(_T("%.5lf"), result);
12 //resultv.Format(TEXT("%lf\n%.2lf"), result);
13 UpdateData(false);
14 }
运行结果:
程序在未发布前挺大的,100多M,包含了很多不用的文件,在资源管理器里还是隐藏的。发布后就几M。
程序发布:将Debug改成Release,运行即发布,之后项目同级目录里有Release文件夹,里面就是你的应用程序啦。