K&R C 逆波兰计算器及相应扩展

在逆波兰表示数中,所有的运算符都跟在操作数后面,例如

(1-2)×(4+5)

采用逆波兰表示法表示为:

1 2 - 4 5 + ×

逆波兰表示法不需要圆括号,只要知道每个运算符需要几个操作数就不会引起歧义

用逆波兰表示法实现计算器采用栈结构.每个操作数都被依次压到栈中;当一个运算符到达时,从栈中弹出相应的操作数,把该运算符作用于弹出的操作数,并把运算结果再压入到栈中.举个例子,对于上面的表达式,首先把1 2 压入栈中,再用再用两者之差取代它们,然后将4 5压入栈中,再用两者之和取代它们,最后从栈中取出栈顶的-1和9,并把它们的积-9压入到栈顶.到达输入行的末尾时,把栈顶的值弹出并打印

由此,我们得到了计算器的基本框架

while(下一个运算符或操作数不是文件结束指示符)

if(是数)

将该数压入到栈中

else if(是运算符)

弹出所需数目的操作数

执行运算

将结果压入栈中

else if(是换行符)

弹出并打印栈顶的值

else

出错

#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<ctype.h>
int getch(void);
void ungetch(int c);
#define	MAXSTACK 100			/*  */			/*  */
double stack[MAXSTACK];	//创建栈	
int sp=0;	//初始化栈指针
void push(double val)	//入栈函数
{
	if ( sp>=MAXSTACK-1 )
		printf ( "error : stack full ,can't push\n" );
	else
		stack[sp++]=val;	//将数据压栈并且使栈指针指向一个空栈位
}
double pop(void)
{
	if( sp==0  )
	{
		printf ( "error : stack empty ,can't pop\n" );
		return 0.0;
	}
	else
		return stack[--sp];	//因为栈指针指向下一个未填入数据的栈位,故使用前缀自减
}
#define	NUMBER '0'			/*标记输入的是数字 */
int getop(char *s)		
{
	int i,c;
	while ( (s[0]=c=getch())==' '||c=='\t' )	//过滤输入操作数前的空白符号
		;			
	s[1]='\0';		//不是很理解,因为后面又继续给s[1]赋值,可能是考虑到输入第一个操作数就是错误的操作数然后直接返回,因而只有s[0]有值,故加上结束符号
	if( !isdigit(c)&&c!='.'  )	//如果输入的第一个数不是数字或者小数点,那么就是一个非法操作数(因为使用的是逆波兰表示法)	
		return c;
	i=0;
	if ( isdigit(c) )	//收集整数部分
		while( isdigit(s[++i]=c=getch())  )		//如果程序能运行到此处(未返回),说明第一个输入的字符是合法并且已经纪录,s[0]中已经有值存在,故使用++i
			;
	if( c=='.'  )
		while( isdigit(s[++i]=c=getch())  )
			;
	s[i]='\0';
	if ( c!=EOF )
		ungetch(c);
	return NUMBER;
}
/* 
 * 关于getch(),ungetch(),K&R C在此处主要是用于模拟后面章节提到的标准库中的getc()和ungetc()
 * 下面是网上找的解释:
 * 数据在输入输出时 要经过缓冲区
 * 当调用getc()的时候 标准输入里的一个字符会被放到缓冲区中 再把缓冲区里的字符赋到变量里
 * 当调用ungetc(c)时候 作为参数的c会被回放到缓冲区中(压回输入总),也就是说,下一个getc()的输入就是刚刚被压回缓冲区的c
 * 为什么在此要用到这两个函数呢?K&R C中的解释如下:
 * 程序中经常会出现这样的情况:
 *	程序不能确定它已经读入的输入时候足够,除非超前多读入一些输入.读入一些字符以合成一个数字的情况便是一个例子:在看到第一个非数字之前
 *	已经读入的数的完整性是不能确定的.由于程序要超前读入一个字符,这样就导致最后有一个字符不属于当前所要读入的数(个人理解:为了避免最后
 *	一个字符是运算符号而无法读取的错误)
 */
#define	BUFSIZE 100		
char buf[BUFSIZE];	//用于ungetch函数的缓冲区
int bufp=0;
int getch(void)
{
	return ((bufp>0)?buf[--bufp]:getchar());
}
void ungetch(int c)
{
	if ( bufp>=BUFSIZE-1 )
		printf ( "ungetch: too many characters\n" );
	else
		buf[bufp++]=c;
}
/* 
 * 到现在,基本实现的简单的四则运算计算器,下面是主函数
 */
#define	MAXOP 100			/* 操作数或运算符的最大长度 */
	int
main ( int argc, char *argv[] )
{
	int type;	
	double op2;
	char s[MAXOP];
	while ( (type=getop(s))!=EOF ) {
		switch ( type ) {
			case NUMBER:	
				push(atof(s));	//输入的是操作数,把操作数转换为double类型并入栈
				break;

			case '+':	
				push(pop()+pop());
				break;

			case '-':	
				op2=pop();		//区分减数和被减数,除法取模运算同理
				push(pop()-op2);
				break;

			case '*':	
				push(pop()*pop());
				break;

			case '/':	
				op2=pop();
				if ( op2!=0.0 )
					push(pop()/op2);
				else
					printf ( "error: zero divisor\n" );
				break;

			case '\n':	
				printf ( "\t%.8g\n",pop() );
				break;

			default:	
				printf ( "error :unknown command %s\n",s );
				break;
		}				/* -----  end switch  ----- */
	}
	return EXIT_SUCCESS;
}				/* ----------  end of function main  ---------- */
<pre name="code" class="cpp">/* 
 * 下面是相应课后练习
 *
 * 练习4-3:在用了基本框架后,对计算器进行扩充就比较简单了,在该程序中加入取摸运算符号,并注意考虑负数情况
 * 	分析:取模运算注意是对浮点数取模.然后加入负数后getop函数需要检查紧跟在符号 - 后面的那个字符,以判断该符号到底代表的是一个减号,还是一个负数
 * 	比如: - 1是一个减号后面跟一个数字,而 -1.23则是一个负数
 * 改进后的getop函数
 */
int getop(char *s)
{
	int c,i;
	while ( (s[0]=c=getch())==' '||c=='\t' )
		;
	s[1]='\0';
	i=0;
	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;
}
/* 练习4.5:给计算器程序增加访问sin,exp,pow等库函数的操作
 * 	分析:主要解决的问题是如何识别输入的sin,exp,pow等符号.库函数名都是小写字母组成,通过这一特点,我们可以从输入的第一个符号入手:字母?表示要引用一个
 * 	库函数,于是进入接收库函数程序部分.
 * 改进后的getop函数
 */
#define	NAME 'n'			/* 标记输入的是一个库函数名  */
int getop(char *s)
{
	int c,i;
	while ( (s[0]=getch())==' '||c=='\t' )
		;
	s[1]='\0';
	i=0;
	if ( islower(c) ) {
		while ( islower(s[++i]=c=getch()) )
			;
		s[i];
		if ( c!=EOF )
			ungetch(c);
		if(strlen(s)>1)
			return NAME;
		else 
			return c;
	}
	if ( !isdigi(c)&&c!='.' )
		return c;
	if ( isdigit(c) )
		while ( isdigit(s[++i]=c=getch()) )
			;
	if( c=='.'  )
		while ( isdigi(s[++i]=c=getch()) )
			;
	s[i]='\0';
	if ( c!=EOF )
		ungetch(c);
	return NUMBER;
}
/*
 */
		case NAME:	
			mathfnc(s);
		break;
void mathfnc(s)
{
	double op2;
	if ( strcmp(s,"sin")==0 )
		push(sin(pop()));
	...以此类推
}
/* 
 * 练习4.10:用getline函数读入整个输入行,在这种情况下可以不使用getch和ungetch函数
 * 分析:使用getline函数后,我们使用数组保存每次输入的一整行输入行,替代了缓冲区
 * getline函数K&R C中一个输入字符串返回字符长度的函数
 */
int getline(char *s,int maxline)
{
	int c,i;
	for ( i=0;i<maxline&&(c=getchar())!=EOF&&c!='\n' ;i++ ) {
		*(s+i)=c;
	}
	*s='\0';
	return i;
}
#define	MAXLINE 100			/*  */
int li=0;
char line[MAXLINE];
int getop(char *s)
{
	int c,i;
	if ( line[li]=='\0' )
		if ( getline(line,MAXLINE)==0 )
			return EOF;
		else
			li=0;
	while ( (s[0]=c=line[i++])==' '||c=='\t' )
		;
	s[1]='\0';
	if ( !isdigit(c)&&c!='.' )
		return c;
	if( isdigit(c)  )
		while ( isdigit(s[++i]=c=line[li++]) )
			;
	if ( c=='.' )
		while ( isdigit(s[++i]=c=line[li++]) )
			;
	s[i]='\0';
	li--;	//用于下一次判断
	return NUMBER;
}


 
 

你可能感兴趣的:(K&R C 逆波兰计算器及相应扩展)