Reverse Polish Calculator (逆波兰计算器)方案的分析——如何解决问题,从需要到实现

Reverse Polish Calculator (逆波兰计算器)方案的分析——如何解决问题,从需要到实现


注:文章素材来源于K&R第二版


需要实现的功能:

(针对用户的期望)

1、基本的四则运算

2、取模运算

*3、其它数学函数的运算。比如三角函数、幂函数、指数函数等

*4、能够调用上一步的结果(待补充)



用户可能的行为:

用户可能的输入(用代表空格,其它不可见字符使用其转义字符表示)

正常输入:

32-\n

3.52.1*\n (带有小数点)

-0.5-3/\n (带有负数)

多余空格:□□\t32-\n

小数点前不带整数:-.5-.6+\n


用户期待的结果:

能够进行多种数学运算,包括小数和负数的运算,三角函数等;

输入回车符后能立即得到正确答案,得到答案后程序不会退出,可以继续计算其他算术式,直到用户输入EOF,结束计算器的运行。



整体框架

while (下一个运算符或者操作数不是EOF )

          if (是数 )

                  将数压入栈中

          else if (是运算符 )

                 弹出所需数目的操作数

                  执行运算

                  将结果压入栈中

         else if (是换行符 )

                 弹出并打印栈顶值

          else

                 出错


    1/*

    2 *逆波兰计算器主函数

    3 */

    4

    5/*

    6 *算法描述:

    7 * while (下一个运算符或者操作数不是EOF )

    8 *  if (是数 )

    9 *      将数压入栈中

   10 *  else if (是运算符 )

   11 *      弹出所需数目的操作数

   12 *      执行运算

   13 *      将结果压入栈中

   14 *  else if (是换行符 )

   15 *      弹出并打印栈顶值

   16 *  else

   17 *      出错

   18 */

   19#include <stdio.h>

   20#include <stdlib.h>

   21#include <math.h>      //使用fmod()函数和其他数学函数

   22#include <string.h>    //使用strcmp()函数

   23

   24#define MAXOP 100

   25#define NUMBER'0'

   26#define NAME'f'

   27

   28intgetop(char s[] );

   29voidpush(double);

   30doublepop(void);

   31voidmathfunc(char s[] );

   32

   33intmain(void)

   34{

   35    int type;

   36    double op2;

   37    char s[MAXOP];

   38

   39    while( (type=getop(s)) != EOF )

   40    {

   41        switch( type)

   42        {

   43            case NUMBER:

   44                push(atof( s) );

   45                break;

   46            case'+':

   47                push(pop() +pop() );

   48                break;

   49            case'*':

   50                push(pop() *pop() );

   51                break;

   52            case'-':

   53                op2=pop();

   54                push(pop() - op2);

   55                break;

   56            case'/':

   57                op2=pop();

   58                if( op2!=0.0)

   59                    push(pop() / op2);

   60                else

   61                    printf("Error : zero divisor\n");

   62                break;

   63            /*

   64             *增加取模运算

   65             */

   66            case'%':

   67                op2=pop();

   68                if( op2!=0.0)

   69                    push(fmod(pop() , op2) );

   70                else

   71                    printf("Error : zero divisor\n");

   72                break;

   73            /*

   74             *增加其他数学函数

   75             */

   76            case NAME:

   77                mathfunc( s);

   78                break;

   79

   80            case'\n':

   81                printf("\t%.8g\n",pop() );

   82                break;

   83            default:

   84                printf("error: unknown command %s\n", s );

   85                break;

   86        }

   87    }

   88    return0;

   89}

   90

   91

   92voidmathfunc(char s[] )

   93{

   94    double op2;

   95

   96    if(strcmp( s,"sin") ==0)

   97        push(sin(pop() ) );

   98    else if(strcmp( s,"cos") ==0)

   99        push(cos(pop() ) );

  100    else if(strcmp( s,"exp") ==0)

  101        push(exp(pop() ) );

  102    else if(strcmp( s,"pow") ==0)

  103    {

  104        op2=pop();

  105        push(pow(pop(), op2) );

  106    }

  107    else

  108        printf("error: %s not supported\n", s );

  109}



如何获取操作数或者运算符(针对用户的行为)

1、获取操作符:基本的四则运算符是单个的字符,直接读入字符。

2、获取操作数:操作数是一系列数字值或者小数点符号或者负号。

3、获取输入结束符:根据用户需求,在读取一个换行符后结束输入,输出结果。

4、对错误输入的处理:读入数字字符和正常的运算符不会产生错误,对于非法的字符,直接读入,交由主函数处理。

具体获取方法:

1、由于针对操作数和运算符读取的方式不一样,操作数需要一个数组来存放一系列字符,而运算符只需要一个字符变量来存放即可,所以读取时需要进行两种类型变量的数据交换,字符变量使用返回值进行交换,字符串可以在函数中进行修改来实现数据交换。由此我们大致可以知道获取操作数和运算符的函数原型:

int getop ( char s[] );

函数返回int型值(实际为char,为了处理EOF所以为int),把一个字符数组作为函数的参数,存储读到的操作数;

2、以具体输入为例进行分析

3.52.1*\n

在这个输入中,我们希望得到两个操作数3.52.1和一个运算符*以及一个结束符\n

2.1首先处理第一个前面的(如果有的话)和关键字符之间的空白或者\t):采取的方法是跳过(连续读取,直至出现第一个不是空白的字符)空白。(空白还有一个作用就是区分负号和减号,在后面会具体谈及。)由于第一个字符可能是操作数的起始部分,而且空白的结束是以读取到下一个不是空白的字符为结束的,这个非空白的字符有可能就是操作数的一部分,所以还是应该将读取的字符存入数组。

2.2、在处理完空白之后,要及时将字符数组变为字符串,以免在其它函数使用时出错。

2.3、如果第一个非空字符不是操作数的组成部分(digit'.''-'),就把这个字符返回

2.4、如果这个字符是操作数的组成部分就“激活”字符数组,继续读取操作数的剩余部分并存入数组中,直到读到下一个不能组成操作数的字符,将字符数组转换为字符串

2.5、处理最后读取的字符,如果它是EOF直接丢弃,否则就把它重新放入输入流,供下一次读取

此时又引发一个问题,就是输入缓存,需要建立一个缓冲区,可以将“误读”的字符重新放到这个缓冲区内,供下一次读取。(稍后详谈)

2.6、正确读取到操作数后,函数需要一个返回值,由之前的假定,它不能是数组,只能是字符常量,但这个字符常量的值并无实际用途,所以只需要一个标签即可,我们把它设置为NUMBER '0'

3负号和减号的区分

当读入的字符是'-'时,如果下一个字符是数字或者小数点,那么这个'-'就是负号,否则就是减号。

4对数学函数的读取

当读入的是一个小写字母时,继续读入小写字母(如果后面有的话),并存入数组,形成字符串,将最后读入的一个不是小写字母的字符(不是EOF)放入输入中,判断字符串的长度,小于1就将读入的一个字符返回,否则就返回一个标志,表示已经读取到函数字符串,并在主程序中使用这种字符串。

 



    1/*

    2 *获取操作符或者操作数的函数

    3 */

    4#include <stdio.h>

    5#include <ctype.h>

    6#include <string.h>

    7

    8#define NUMBER'0'

    9#define NAME'f'

   10

   11intgetch(void);

   12intungetch(int c);

   13

   14intgetop(char s[] )

   15{

   16    int i, c;

   17    while( (s[0] = c =getch() ) ==' '  ||  c=='\t')

   18        ;

   19    s[1] = '\0';

   20    i=0;

   21

   22    /*

   23     *增加对其他函数字符串的读取

   24     */

   25

   26    if(islower( c) )

   27    {

   28        while(islower( s[++i] = c =getch() ) )

   29            ;

   30        s[i] = '\0';

   31        if( c!= EOF)

   32            ungetch( c);

   33        if(strlen( s) >1)

   34            return NAME;

   35        else

   36            return c;

   37    }

   38

   39    if( !isdigit( c)  &&  c!='.'  &&  c!='-')

   40        return c;

   41

   42    /*

   43     *增加对负号和减号的区分

   44     */

   45    if( c=='-')

   46        if(isdigit( c=getch() )  ||  c=='.')

   47            s[++i] = c;

   48        else

   49        {

   50            if( c!= EOF)

   51                ungetch( c);

   52            return'-';

   53        }

   54

   55    if(isdigit( c) )

   56        while(isdigit( s[++i] = c =getch() ) )

   57            ;

   58    if( c=='.')

   59        while(isdigit( s[++i] = c =getch() ) )

   60            ;

   61

   62    s[i] ='\0';

   63

   64    if( c!= EOF)

   65        ungetch( c);

   66

   67    return NUMBER;

   68}



如何实现栈(数据的组织)

我们把操作数都放到一个数组中,只要程序遇到操作符,就会将相应个数的操作数取出来,进行运算,然后将结果再次放入数组中。这种基于数组的“操作方式”(栈,实际上就是数组或者链表等,由于对其操作方式的不同,就会产生栈、列队等各种数据结构)就是栈。

它起码应该具备两个基本功能:进栈和出栈。

我们需要一个数组来存储数据,还要一个位置量来标记栈顶位置,方便进出栈。



    1/*

    2 *用于栈的操作函数

    3 */

    4#include <stdio.h>

    5#define MAXVAL 100

    6

    7int sp=0;

    8double val[MAXVAL];

    9

   10voidpush(double f)

   11{

   12    if( sp< MAXVAL)

   13        val[sp++] = f;

   14    else

   15        printf("error: stack full\n");

   16}

   17

   18doublepop(void)

   19{

   20    if( sp>0)

   21        return val[--sp];

   22    else

   23    {

   24        printf("error: empty stack\n");

   25        return0.0;

   26    }

   27}




如何实现输入缓存

我们需要一个栈(存取都发生在数组的同一端),当发生“误读”时,就将这个字符推进栈中,下一次读取时,如果栈不是空,就读取栈顶元素,否则就从标准输入读取。




    1/*

    2 *读取字符和放入被读取字符的函数

    3 */

    4#include <stdio.h>

    5#define BUFSIZE 100

    6

    7char buf[BUFSIZE];

    8int bufp=0;

    9

   10intgetch(void)

   11{

   12    return( bufp>0) ? buf[--bufp] :getchar();

   13}

   14

   15voidungetch(int c)

   16{

   17    if( bufp>= BUFSIZE)

   18        printf("ungetch: too many characters\n");

   19    else

   20        buf[ bufp++ ] = c;

   21}




最后提供源代码供大家编译运行


/*
 * 逆波兰计算器主函数
 */

/*
 * 算法描述:
 * while ( 下一个运算符或者操作数不是EOF )
 * 	if ( 是数 )
 * 		将数压入栈中
 * 	else if ( 是运算符 )
 * 		弹出所需数目的操作数
 * 		执行运算
 * 		将结果压入栈中
 * 	else if ( 是换行符 )
 * 		弹出并打印栈顶值
 * 	else
 * 		出错
 */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>		//使用fmod()函数和其他数学函数
#include <string.h>		//使用strcmp()函数

#define MAXOP 100
#define NUMBER '0'
#define NAME 'f'

int getop ( char s[] );
void push ( double );
double pop ( void );
void mathfunc ( char s[] );

int main ( void )
{
	int type;
	double op2;
	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 );
				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 NAME:
				mathfunc ( s );
				break;

			case '\n':
				printf ( "\t%.8g\n", pop () );
				break;
			default:
				printf ( "error: unknown command %s\n", s );
				break;
		}
	}
	return 0;
}


void mathfunc ( 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 <stdio.h>
#include <ctype.h>
#include <string.h>

#define NUMBER '0'
#define NAME 'f'

int getch ( void );
int ungetch ( int c );

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;
}

/*
 * 用于栈的操作函数
 */
#include <stdio.h>
#define MAXVAL 100

int sp = 0;
double val[MAXVAL];

void push ( double f )
{
	if ( sp < MAXVAL )
		val[sp++] = f;
	else
		printf ( "error: stack full\n" );
}

double pop ( void )
{
	if ( sp > 0 )
		return val[--sp];
	else
	{
		printf ( "error: empty stack\n" );
		return 0.0;
	}
}


/*
 * 读取字符和放入被读取字符的函数
 */
#include <stdio.h>
#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;
}


  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值