逆波兰计算器(C语言)

   源自《The C Programming Language》 P62 ex4.3:

     计算例如:(1 - 2) * (4 + 5)的值,采用逆波兰表示法(即后缀表示法)

   代码:

     main.c

View Code
1 #include <stdio.h>
2 #include <stdlib.h> //为了使用库函数atof
3 #include <math.h> //使用sin, exp, pow等数学函数
4 #include <string.h> //使用strcmp, strlen等字符串函数
5 #include "getop.h"
6
7 #define MAXOP 100 //操作数或运算符的最大长度(待处理字符串的最大长度)
8 #define NUMBER '0' //标识找到一个数
9 #define NAME 'n' //标示找到一个数学函数
10
11 void push(double );
12 double pop();
13 //void printStack(double []);
14 void clear();
15 void mathfnc(char []);
16
17 //extern double val[]; //如果声明为extern val[]; 则报错:变量val被重定义
18 //extern sp;
19
20 //逆波兰计算器
21 int main()
22 {
23
24 int type;
25 int i;
26 int var;
27 double op2;
28 double op1;
29 double variable[26];
30 double v;
31 //double tmp;
32 char s[MAXOP];
33
34 var = 0;
35 for(i = 0; i < 26; ++i)
36 variable[i] = 0.0;
37 while((type = getop(s)) != EOF)
38 {
39 switch(type)
40 {
41 case NUMBER: //当待处理字符串是数值字符串时,将其转换,并压栈
42 push(atof(s));
43 break;
44
45 case '+':
46 push(pop() + pop());
47 break;
48
49 case '*':
50 push(pop() * pop());
51 break;
52
53 case '-':
54 op2 = pop();
55 push(pop() - op2); //push(pop() - pop());是错误的,虽然算法运算符中操作数的结合方式是从左到右
56 //但是不能确定push参数中左边的pop函数一定比右边的pop函数先执行
57 break;
58
59 case '/':
60 op2 = pop();
61 if(op2 != 0.0)
62 push(pop() / op2);
63 else
64 {
65 printf("error: divide 0.0!");
66 return -1;
67 }
68 break;
69
70 case '%':
71 op2 = pop();
72 if(op2 != 0.0)
73 push(fmod(pop(), op2));
74 else
75 printf("error: mod 0.0!");
76 break;
77
78 case '=':
79 pop();
80 if(var >= 'A' && var <= 'Z')
81 {
82 variable[var - 'A'] = pop();
83 push(variable[var - 'A']);
84 }
85 else
86 printf("error: no variable name\n");
87 break;
88
89 case '\n': //当键入换行符时,打印输出栈顶元素
90 /*if(sp > 0)
91 printStack(val);
92 else
93 printf("error: stack empty!\n");
94 */
95 v = pop();
96 printf("the result = %.8g\n", v);
97 break;
98
99 case 'p': //不出栈的情况下,打印栈顶元素
100 op2 = pop();
101 printf("the top element of stack = %f\n", op2);
102 push(op2);
103 break;
104
105 case 'd': //复制栈顶元素
106 op2 = pop();
107 //tmp = op2;
108 //printf("the duplication of top element = %f\n", op2);
109 push(op2);
110 push(op2);
111 printf("the duplication of top element = %f\n", op2);
112 break;
113
114 /*case 'S':
115 push(sin(pop()));
116 break;
117
118 case 'E':
119 push(exp(pop()));
120 break;
121
122 case 'P':
123 op2 = pop();
124 push(pow(pop(), op2));
125 break;
126 */
127
128 case NAME: //处理数学函数分支,这样比上面分别用每个命令来定义一个函数要通用,并容易扩展
129 mathfnc(s);
130 break;
131
132 case 's': //交换栈顶元素
133 op2 = pop();
134 op1 = pop();
135 push(op1);
136 push(op2);
137 break;
138
139 case 'c': //清空堆栈
140 clear();
141 break;
142
143 default:
144 if(type >= 'A' && type <= 'Z')
145 push(variable[type - 'A']);
146 else if(type == 'v')
147 push(v);
148 else
149 printf("error: unknown command %s", s);
150 break;
151
152 }
153 var = type;
154 }
155
156 return 0;
157 }
158
159 #define MAXVAL 100 //栈val的最大深度
160
161 int sp = 0; //栈中的下一个空闲的位置
162 double val[MAXVAL]; //值栈
163
164 void push(double f) //把f压入值栈中
165 {
166 if(sp < MAXVAL)
167 val[sp++] = f;
168 else
169 printf("error: stack full, can't push %g\n", f);
170 }
171
172 double pop() //从值栈中弹出并返回栈顶的值
173 {
174 if(sp > 0)
175 return val[--sp];
176 else
177 {
178 printf("error: stack empty, can't pop\n");
179 return 0.0;
180 }
181 }
182
183 /*void printStack(double* val)
184 {
185 printf("top of stack = %f\n", val[sp-1]);
186 }
187 */
188
189 void clear() //清空值栈
190 {
191 sp = 0;
192
193 return;
194
195 }
196
197 void mathfnc(char s[]) //数学函数处理的通用接口
198 {
199 double op2;
200
201 if(strcmp(s, "sin") == 0)
202 push(sin(pop()));
203 else if(strcmp(s, "cos") == 0)
204 push(cos(pop()));
205 else if(strcmp(s, "exp") == 0)
206 push(exp(pop()));
207 else if(strcmp(s, "pow") == 0)
208 {
209 op2 = pop();
210 push(pow(pop(), op2));
211 }
212 else
213 printf("error: %s not supported!\n", s);
214
215 }

   getop.c

View Code
1 #include <stdio.h>
2 #include <ctype.h>
3 #include <string.h>
4 #include "getop.h"
5
6 //extern NUMBER;
7 #define NUMBER '0'
8 #define NAME 'n'
9
10 int getop(char s[]) //获取下一个运算符或操作数
11 {
12 int i;
13 int c;
14
15 while((s[0] = c = getch()) == ' ' || c == '\t')
16 ;
17 s[1] = '\0';
18 i = 0;
19 if(c != '-' && !islower(c) && !isdigit(c) && c != '.') //判断是否属于这四种情况,如不是,下面分别对这四种情况处理
20 return c; //当是运算符或大写字母时,返回其ASCII值
21 if(c == '-')
22 if(isdigit(c = getch()) || c == '.')
23 s[++i] = c;
24 else
25 {
26 if(c != EOF)
27 ungetch(c);
28 return '-';
29 }
30 if(islower(c))
31 {
32 while(islower(s[++i] = c = getch()))
33 ;
34 s[i] = '\0';
35 if(c != EOF)
36 ungetch(c);
37 if(strlen(s) > 1)
38 return NAME;
39 else
40 return s[0];             //错误:return c;例: s = "v ",如果用return c;则会返回空格,而本意是返回v
41 }
42 if(isdigit(c))
43 while(isdigit(s[++i] = c = getch())) //收集整数部分
44 ;
45 if(c == '.')
46 while(isdigit(s[++i] = c = getch())) //收集小数部分
47 ;
48 s[i] = '\0';
49 if(c != EOF)
50 ungetch(c);
51
52 return NUMBER; //当是操作数时,返回NUMBER,标识这种情况
53 }
54
55 #define BUFSIZE 100 //缓冲区的最大长度
56
57 //int buf[BUFSIZE]; //这样可以正确处理压回EOF(-1)及其他任何负数的情况
58 char buf[BUFSIZE]; //用于ungetch函数的缓冲区
59 int bufp = 0; //buf中下一个空闲位置
60
61 int getch() //取一个字符(可能是要压回的字符)
62 {
63 return (bufp > 0) ? buf[--bufp] : getchar();
64 }
65
66 void ungetch(int c) //把字符压回到输入(缓冲区)中
67 {
68 if(bufp >= BUFSIZE)
69 printf("ungetch: too many characters\n");
70 else
71 buf[bufp++] = c;
72 }
73
74 /************************************************************************
75 另一种getch ungetch函数,最多只压回一个字符,则缓冲区大小为1个字节即可
76 *************************************************************************
77 char buf = 0;
78
79 int getch()
80 {
81 int c;
82
83 if(buf != 0)
84 c = buf;
85 else
86 c = getchar();
87 buf = 0;
88
89 return c;
90 }
91
92 void ungetch(int c)
93 {
94 if(buf != 0)
95 printf("error: too many characters\n");
96 else
97 buf = c;
98 }
99 ***********************************************************************/
100
101 /**********************************************************************
102 另一种getop函数:不使用函数ungetch,而是使用一个static变量来代替缓冲区
103 ***********************************************************************
104 int getop(char s[]) //获取下一个运算符或操作数
105 {
106 int i;
107 int c;
108 static int lastc = 0;
109
110 if(lastc == 0)
111 c = getch();
112 else
113 {
114 c = lastc;
115 lastc = 0;
116 }
117 while((s[0] = c) == ' ' || c == '\t')
118 c = getch();
119 s[1] = '\0';
120 i = 0;
121 if(c != '-' && !islower(c) && !isdigit(c) && c != '.') //判断是否属于这四种情况,如不是,下面分别对这四种情况处理
122 return c; //当是运算符或大写字母时,返回其ASCII值
123 if(c == '-')
124 if(isdigit(c = getch()) || c == '.')
125 s[++i] = c;
126 else
127 {
128 if(c != EOF)
129 lastc = c;
130 return '-';
131 }
132 if(islower(c))
133 {
134 while(islower(s[++i] = c = getch()))
135 ;
136 s[i] = '\0';
137 if(c != EOF)
138 lastc = c;
139 if(strlen(s) > 1)
140 return NAME;
141 else
142 return s[0]; //错误:return c;例: s = "v ",如果用return c;则会返回空格,而本意是返回v
143 }
144 if(isdigit(c))
145 while(isdigit(s[++i] = c = getch())) //收集整数部分
146 ;
147 if(c == '.')
148 while(isdigit(s[++i] = c = getch())) //收集小数部分
149 ;
150 s[i] = '\0';
151 if(c != EOF)
152 lastc = c;
153
154 return NUMBER; //当是操作数时,返回NUMBER,标识这种情况
155 }
156 ***********************************************************************/

  getop.h   

View Code
1 #ifndef _GETOP_H_
2 #define _GETOP_H_
3
4 //#include <stdio.h>
5 //#include <stdlib.h>
6 //#include <ctype.h>
7
8 //#define MAXOP 100
9 //#define NUMBER '0'
10
11 int getch();
12 void ungetch(int);
13 int getop(char []);
14
15 #endif

   分析:

     1,  程序设计:在设计本程序时,首先进行模块划分,

          main.c:main函数 实现操作数压栈,出栈,算术运算,数学运算,打印 复制 交换栈顶元素等基本操作;

                       push函数 实现将double型数据压入值栈val中;

                       pop函数 实现将值栈val中的栈顶元素出栈;

                       clear函数 实现清空值栈val;

                       mathfnc函数 实现sin,cos, exp, pow等数学操作(调用math.h中的这些库函数来处理val中的数据

                       并非自定义上述函数);

    getop.c:  getop函数 实现从输入中获取一个操作数或操作符(* + - / % sin d 等操作符);

                           getch函数 实现从自定义的输入缓冲区(buf)或操作系统定义的输入缓冲区中读入一个字符;

                           ungetch函数 实现将字符压回到自定义的输入缓冲区(buf)中;

   getop.h:  声明getop getch ungetch函数。

     2,  main函数中通过while((type = getop(s)) != EOF)处理每次从待处理的输入字符串中获取的s,这是程序的主干部分

          在确定type != EOF时,通过switch - case 语句分别处理当type为  NUMBER + - * / % /n p d NAME s c default

          等情况。

     3,  对于 -  / % 情况不能像 + * 情况直接使用push(pop() - pop()),因为不满足交换律,

          虽然算法运算符中操作数的结合方式是从左到右,但是不能确定push参数中左边的pop函数一定比右边的pop函数先执行

     4,  getop函数,通过while((s[0] = c = getch()) == ' ' || c == '/t') ; 来跳过s头部的空白字符(空格,水平制表符),

          每次对于第一个字符c通过判断(c != '-' && !islower(c) && !isdigit(c) && c != '.')这四种情况来分别处理,

          若上述条件成立,表明c是一个例如 + - * /等单字符的操作符;

          然后分四种情况:c == '-', islower(c), isdigit(c), c == '.' 进行处理。

          注意:将最后一个读入的不符合条件的字符压回到自定义的输入缓冲区中。

      getch函数: (bufp > 0) ? buf[--bufp] : getchar(); 从自定义输入缓冲区或OS定义的输入缓冲区中读入一个字符

      ungetch函数:把字符压回到自定义的输入缓冲区中

     5,  在合适的位置定义变量,例如:getop.c中BUFSIZE,buf,bufp在getop函数没有用到,而只在getch及ungetch函数

          中用到,故其定义的位置在getop函数之后,而在getch函数之前,这样就可以防止在getop函数中出现无意修改上述变量

          的可能。

          确定某些文件用到哪些头文件,例如在在getop.c中用到isdigit等判断字符的函数,故在它里面添加ctype.h头文件,而在

          main.c中不用到ctype.h中的库函数,故在main.c中不添加ctype.h头文件。

     6,  如果想要ungetch函数正确处理压回的EOF或其他任何负数,则将输入缓冲区buf设置为int buf[BUFSIZE],即缓冲区的

          数据类型为int型而不是char型。

          C语言不要求char变量是signed或unsigned类型的,当把一个char型变量转换成int型变量,结果可能为正也可能为负,

          例如,十进制的-1被表示为十六进制为0XFFFF(假定为一台16位机),当把0XFFFF保存到一个char型变量里去时,实际

          被保存的数字是0XFF,当把0XFF转换成一个int型数据时,它可能被转换成0X00FF(255),也可能被转换成0XFFFF(-1)

          所以打算对待像其他字符那样对待EOF时,应该把输入缓冲区buf声明成一个int型数组。

          注:在某些机器上,如果一个char型变量的最高(左)二进制位为1,那么把它转换成一个int型数据时,就会在它的高位上

                添加一系列1,这样得到的结果为负数;

                在另一些机器上,当需要把一个char型变量转换成一个int型数据时,系统会在它的高位上添加一系列0,这样不管被

                转换的char型变量的最高位是1还是0,结果永远是个正数。

posted on 2011-06-08 01:29  将军之盾  阅读(1533)  评论(5编辑  收藏  举报