这两天在看一个C语言写的计算器程序,做了不少的功夫,跟着作者一步步的进行完善,了解了许多细节性的东西,在此自己做个总结,加深自己对程序的印象,也算是梳理。
在该计算器程序,能进行加减乘除、sin、cos、exp等操作,同时能进行数值保存功能。而该计算器使用逆波兰表示法。即所有运算符都跟在操作数的后面,比如下列表达式:
(1 - 2) * (4 + 5)采用逆波兰表示法表示为:1 2 - 4 5 + *
逆波兰表达法中不需要圆括号,只要知道每个运算符需要几个操作数就不会引起歧义。
计算器程序实现很简单,具体原理如下:
while(/* 下一个运算符或操作数不是文件结束指示符 */) if(/* 是数 */) /* 将该数压入到栈中 */ else if (/* 是运算符 */) /* 弹出所需数目的操作数 */ /* 执行运算 */ /* 将结果压入到栈中 */ else if (/* 是换行符 */) /* 弹出并打印栈顶的值 */ else /* 出错 */
在程序设计中,使用模块化思想,getop函数来进行读入,该函数返回一个标识,用来标识读入的是什么类型。主循环体中根据该标识执行相应的动作。
以下是该程序: (我将所有函数和变量放在同一文件)
#include <stdlib.h> #include <stdio.h> #include <string.h> #define MAXOP 100 #define NUMBER '0' //标识读入的是数字 #define NAME 'n' //标识读入的是字符串(函数名或非法字符串) #define ALPHA 26 int getop(char []); void push (double); //压栈 double pop(void); //出栈 void clear(void); //清空栈 void mathfnc(char []); //执行相应的数学函数sin、cos、exp等 int main(void) { int type; int i, var = 0; double op1, op2,v; char s[MAXOP]; double variable[ALPHA]; for (i = 0; i < ALPHA; i++) //初始化用于保存数值的变量数组 variable[i] = 0.0; while ((type = getop(s)) != EOF) //读取输入 { switch (type) { case NUMBER: push (atof(s)); break; case NAME: mathfnc(s); break; case '+': push (pop() + pop()); break; case '*': push (pop() * pop()); break; case '-': op2 = pop(); push (pop() - op2); break; case '/': op2 = pop(); if (op2 != 0.0) push (pop() / op2); else printf ("error: zero divisor\n"); break; case '%': op2 = pop(); if (op2 != 0.0) push (fmod(pop(), op2)); else printf ("error: zero divisor\n"); break; case '?': //打印栈顶元素 op2 = pop(); printf ("\t%.8g\n", op2); push (op2); break; case '=': //保存数值 pop(); if (var >= 'A' && var <= 'Z') variable[var - 'A'] = pop(); else printf ("error: no variable name\n"); break; case 'c': clear(); break; case 'd': //复制栈顶元素 op2 = pop(); push(op2); push(op2); break; case 's': //交换栈元素 op1 = pop(); op2 = pop(); push(op1); push(op2); case '\n': v = pop(); //v保存最后的一次结果 printf ("\t%.8g\n", v); break; default: if (type >= 'A' && type <= 'Z') push(variable[type - 'A']); else if (type == '@') //输入的字符@表示最近一次结果值 push(v); else printf ("error: unknown command %s\n", s); break; } var = type; } return 0; } /* ----------------------------------------------------------- */ #define MAXVAL 100 int sp = 0; //标识栈顶 double val[MAXVAL]; void push(double f) { if (sp < MAXVAL) val[sp++] = f; else printf ("error: stack full, can't push %g\n", f); } double pop(void) { if (sp > 0) return val[--sp]; else { printf ("error: statck empty\n"); return 0.0; } } void clear(void) { sp = 0; } void mathfnc (char s[]) { double op2; if (strcmp (s, "sin") == 0) push(sin(pop())); else if(strcmp (s, "cos") == 0) push(cos(pop())); else if(strcmp (s, "exp") == 0) push(exp(pop())); else if(strcmp (s, "pow") == 0) { op2 = pop(); push (pow(pop(), op2)); } else printf ("error: %s not supported\n", s); } /* ----------------------------------------------------------- */ #include <ctype.h> int getch(void); void ungetch(int); int getop(char s[]) { int i, c; while ((s[0] = c = getch()) == ' ' || c == '\t') //过滤开头的空白字符 ; s[1] = '\0'; i = 0; if (islower(c)) //判断是否为小写字母,也即读取由小写字母组成的字符串 { while (islower(s[++i] = c = getch())) ; s[i] = '\0'; if (c != EOF) ungetch(c); if (strlen (s) > 1) return NAME; else return c; } if (!isdigit(c) && c != '.' && c != '-') return c; if (c == '-') //用于判断是负数还是减操作 { if (isdigit(c = getch()) || c == '.') s[++i] = c; else { if (c != EOF) ungetch(c); return '-'; } } if (isdigit(c)) //收集整数部分 while (isdigit(s[++i] = c = getch())) ; if (c == '.') //收集小数部分 while (isdigit(s[++i] = c = getch())) ; s[i] = '\0'; if (c != EOF) ungetch(c); return NUMBER; } /* ----------------------------------------------------------- */ /* * 引用以下两个函数是因为:程序不能确定它已经读入的输入是否足够 * * 除非超前多读入一些输入,在本程序中,读入一些字符合成一个数字 * * 所以在看到第一个非数字字符之前,已经读入的数的完整性是不能确定的 * 由于程序要超前读入一个字符,这样就导致最后又一个字符不属于当前所要读入的数 */ #define BUFSIZE 100 char buf[BUFSIZE]; int bufp = 0; int getch(void) { return (bufp > 0) ? buf[--bufp] : getchar(); } void ungetch (int c) { if (bufp >= BUFSIZE) printf ("ungetch: too many characters\n"); else buf[bufp++] = c; }
该程序虽然简单,但是还是存在一些小小的问题,比如没有数据时进行pop的话,会打印栈中无数据同时返回数值0.0,在循环体中许多执行操作会将该数值保存到栈中,之后打印该值,用户体验度比较差。程序设计方面,模块化设计使得该程序容易增加功能而不影响其他模块,比如增加一些数学函数处理,在mathfnc函数中去添加,或增加一些运算符操作,可以在主循环体中增加。
总之,这次学习还是颇有收获。
关于计算器的精彩文章请查看《计算器专题》 ,更多精彩等你来发现!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持自学编程网。
- 本文固定链接: https://zxbcw.cn/post/181555/
- 转载请注明:必须在正文中标注并保留原文链接
- QQ群: PHP高手阵营官方总群(344148542)
- QQ群: Yii2.0开发(304864863)