源自《The C Programming Language》 P62 ex4.3:
计算例如:(1 - 2) * (4 + 5)的值,采用逆波兰表示法(即后缀表示法)
代码:
main.c
#include
#include //为了使用库函数atof
#include //使用sin, exp, pow等数学函数
#include //使用strcmp, strlen等字符串函数
#include "getop.h"
#defineMAXOP100//操作数或运算符的最大长度(待处理字符串的最大长度)
#defineNUMBER'0'//标识找到一个数
#defineNAME'n' //标示找到一个数学函数
void push(double );
double pop();
//void printStack(double []);
void clear();
void mathfnc(char []);
//extern double val[];//如果声明为extern val[]; 则报错:变量val被重定义
//extern sp;
//逆波兰计算器
int main()
{
int type;
double op2;
double op1;
//double tmp;
char s[MAXOP];
while((type = getop(s)) != EOF)
{
switch(type)
{
case NUMBER://当待处理字符串是数值字符串时,将其转换,并压栈
push(atof(s));
break;
case '+':
push(pop() + pop());
break;
case '*':
push(pop() * pop());
break;
case '-':
op2 = pop();
push(pop() - op2);//push(pop() - pop());是错误的,虽然算法运算符中操作数的结合方式是从左到右
//但是不能确定push参数中左边的pop函数一定比右边的pop函数先执行
break;
case '/':
op2 = pop();
if(op2 != 0.0)
push(pop() / op2);
else
{
printf("error: divide 0.0!");
return -1;
}
break;
case '%':
op2 = pop();
if(op2 != 0.0)
push(fmod(pop(), op2));
else
printf("error: mod 0.0!");
break;
case '/n'://当键入换行符时,打印输出栈顶元素
/*if(sp > 0)
printStack(val);
else
printf("error: stack empty!/n");
*/
printf("the result = %.8g/n", pop());
break;
case 'p'://不出栈的情况下,打印栈顶元素
op2 = pop();
printf("the top element of stack = %f/n", op2);
push(op2);
break;
case 'd'://复制栈顶元素
op2 = pop();
//tmp = op2;
//printf("the duplication of top element = %f/n", op2);
push(op2);
push(op2);
printf("the duplication of top element = %f/n", op2);
break;
/*case 'S':
push(sin(pop()));
break;
case 'E':
push(exp(pop()));
break;
case 'P':
op2 = pop();
push(pow(pop(), op2));
break;
*/
case NAME://处理数学函数分支,这样比上面分别用每个命令来定义一个函数要通用,并容易扩展
mathfnc(s);
break;
case 's'://交换栈顶元素
op2 = pop();
op1 = pop();
push(op2);
push(op1);
break;
case 'c'://清空堆栈
clear();
break;
default:
printf("error: unknown command %s", s);
break;
}
}
return 0;
}
#defineMAXVAL100//栈val的最大深度
int sp = 0;//栈中的下一个空闲的位置
double val[MAXVAL];//值栈
void push(double f)//把f压入值栈中
{
if(sp < MAXVAL)
val[sp++] = f;
else
printf("error: stack full, can't push %g/n", f);
}
double pop()//从值栈中弹出并返回栈顶的值
{
if(sp > 0)
return val[--sp];
else
{
printf("error: stack empty, can't pop/n");
return 0.0;
}
}
/*void printStack(double* val)
{
printf("top of stack = %f/n", val[sp-1]);
}
*/
void clear()//清空值栈
{
sp = 0;
return;
}
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);
}
getop.c
#include
#include
#include
#include "getop.h"
//extern NUMBER;
#defineNUMBER'0'
#defineNAME'n'
int getop(char s[])//获取下一个运算符或操作数
{
int i;
int c;
while((s[0] = c = getch()) == ' ' || c == '/t')
;
s[1] = '/0';
i = 0;
if(c != '-' && !islower(c) && !isdigit(c) && c != '.')//判断是否属于这四种情况,如不是,下面分别对这四种情况处理
return c;//当是运算符时,返回此运算符的ASCII值
if(c == '-')
if(isdigit(c = getch()) || c == '.')
s[++i] = c;
else
{
if(c != EOF)
ungetch(c);
return '-';
}
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 s[0]; //错误:return c; 例:s = "v ",则
//返回空格,而本意是返回v
}
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;//当是操作数时,返回NUMBER,标识这种情况
}
#defineBUFSIZE100//缓冲区的最大长度
//int buf[BUFSIZE];//这样可以正确处理压回EOF(-1)及其他任何负数的情况
char buf[BUFSIZE];//用于ungetch函数的缓冲区
int bufp = 0;//buf中下一个空闲位置
int getch()//取一个字符(可能是要压回的字符)
{
return (bufp > 0) ? buf[--bufp] : getchar();
}
void ungetch(int c)//把字符压回到输入(缓冲区)中
{
if(bufp >= BUFSIZE)
printf("ungetch: too many characters/n");
else
buf[bufp++] = c;
}
getop.h
#ifndef_GETOP_H_
#define_GETOP_H_
//#include
//#include
//#include
//#defineMAXOP100
//#defineNUMBER'0'
int getch();
void ungetch(int);
int getop(char []);
#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,结果永远是个正数。