表达式求值,栈的应用(C语言)

本文详细介绍了如何使用栈数据结构解析含有基本四则运算的数学表达式,包括实现步骤、代码解析及解决输入逻辑问题的过程。特别关注了如何处理多位数输入与运算符优先级的问题,通过在接收数字时加入临时存储和结束符来确保正确的运算顺序。
摘要由CSDN通过智能技术生成

无语死了,好在终于弄出来了。


题目:利用栈编写表达式求值程序:输入含有“+”、“-”、“*”、“/”四则运算的表达式,其中负数要用(0-正数)表示,并以=结束。要求输出表达式的值(两运算符号的有限关系见教材《数据结构》表3.1)。


测试样式:

【第一组自测数据】

【键盘输入】

3*(9-7)#↙

【正确输出】

6


【第二组自测数据】

【键盘输入】

(0-12)*((5-3)*3)/(2+2)=

【正确输出】

-18


完整代码

typedef int Status; 
typedef char SElemType;
typedef double SElemType2;
#include"malloc.h" 
#include"stdio.h"
#include"math.h"
#include"stdlib.h"
#include"string.h"
#define OK              1
#define ERROR           0
#define TRUE            1
#define FALSE           0
#define STACK_INIT_SIZE 10 //存储空间初始分配量
#define STACKINCREMENT  2  //存储空间分配增量

///
//gets了字符串后,要将字符串中的【运算符】和【运算数】分开。	   
//故而要建立两个栈,一个存储【运算符】,另一个存储【运算数】。	   
//【运算符栈】为char类型,【运算数栈】为double类型。			   
//因此,相应的,对于两个种同类型的栈,就要有两种不同类型的基本操作。
///

struct SqStack       //运算符型的
{
	SElemType * base;
	SElemType * top;
	int stacksize;
};

struct SqStack2		 //运算数型的
{
	SElemType2 * base;
	SElemType2 * top;
	int stacksize;
};

Status InitStack(SqStack &S)   //运算符栈的初始化操作
{
	S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType));
	if(!S.base) exit(-2);
	S.top = S.base;
	S.stacksize = STACK_INIT_SIZE;
	return OK;
}

Status InitStack2(SqStack2 &S)  //运算数栈的初始化操作
{
	S.base = (SElemType2 *)malloc(STACK_INIT_SIZE * sizeof(SElemType2));
	if(!S.base) exit(-2);
	S.top = S.base;
	S.stacksize = STACK_INIT_SIZE;
	return OK;
}

Status Push(SqStack &S, SElemType e)  //运算符栈的进栈操作
{
	if(S.top - S.base >= S.stacksize)
	{
		S.base = (SElemType *)realloc(S.base,(S.stacksize + STACKINCREMENT) * sizeof (SElemType));
		if(!S.base) exit(-2);
		S.top = S.base + S.stacksize;
		S.stacksize += STACKINCREMENT;
	}
	* S.top = e;
	S.top ++;
	return OK;
}

Status Push2(SqStack2 &S, SElemType2 e)  //运算数栈的进栈操作
{
	if(S.top - S.base >= S.stacksize)
	{
		S.base = (SElemType2 *)realloc(S.base,(S.stacksize + STACKINCREMENT) * sizeof (SElemType2));
		if(!S.base) exit(-2);
		S.top = S.base + S.stacksize;
		S.stacksize += STACKINCREMENT;
	}
	* S.top = e;
	S.top ++;
	return OK;
}

Status In(char c)   //判断是否为运算符,是运算符则返回 TRUE, 否则返回 FALSE
{
	if(c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')' || c == '=') return TRUE;
	else return FALSE;
}

SElemType GetTop(SqStack S)  //返回运算符栈的栈顶元素
{
	SElemType *p;
	p = S.top;
	p--;
	if(S.base == S.top) return ERROR;
	return(*p);
}

SElemType2 GetTop2(SqStack2 S)  //返回运算数栈的栈顶元素
{
	SElemType2 *p;
	if(S.base == S.top) return ERROR;
	p = S.top;
	p--;
	return(*p);
}

Status Pop(SqStack &S, SElemType &e)  //运算符栈的退栈操作
{
	if(S.top == S.base) return ERROR;
	S.top --;
	e = * S.top;
	return OK;
}

Status Pop2(SqStack2 &S, SElemType2 &e)	//运算数栈的退栈操作
{
	if(S.top == S.base) return ERROR;
	S.top --;
	e = * S.top;
	return OK;
}


//判定运算符栈的栈顶运算符 op1 与读入的运算符 op2 之间有限关系的函数
//这个函数颇蠢,看了别人写的感觉简单明了,不过懒得改了
SElemType Precede(SElemType op1, SElemType op2)	
{												
	switch(op1)
	{
		case '+':if(op2 == '+' || op2 == '-' || op2 == ')' || op2 == '=') return ('>');
				 else return ('<'); break;
		case '-':if(op2 == '+' || op2 == '-' || op2 == ')' || op2 == '=') return ('>');
				 else return ('<'); break;
		case '*':if(op2 == '(') return ('<');
				 else return ('>'); break;
		case '/':if(op2 == '(') return ('<');
				 else return ('>'); break;
		case '(':if(op2 == ')') return ('=');
				 else return ('<'); break;
		case ')':return ('>'); break;
		case '=':if(op2 == '=') return ('=');
			     else return ('<'); break;
	}
}

double Operate(SElemType2 a, SElemType theta, SElemType2 b) //四则运算 a,b为运算数theta 为运算符。
{															//形式为 a theta b
	double an;
	switch(theta)
	{
	case'+':an = a + b; break;
	case'-':an = a - b; break;
	case'*':an = a * b; break;
	case'/':an = a / b; break;
	}
	return(an);   //注意返回值为double型
}

/
//这里要考虑到输入的表达式中有两位数及以上的情况 			  
//如该表达式   12*123=                        			   
//但表达式又是以字符的形式一个一个字符的接收    			   
//即'1','2'是分开的,不是一下子接收'12'         			   
//故而要加一段代码,主要思想为: 				 			   
//在接收到一个数字的时候						 			   
//先暂存起来        					 	     			   
//接收下一个看看是不是还是数字				 			   
//是的话就要和前面这个组合起来							   
//一旦下一个不是,就用atof 将TempData中的字符转为double型数据 
/

SElemType2 EvaluateExpression()
{
	SqStack OPTR;
	SqStack OPND;
	InitStack(OPTR);Push(OPTR, '=');  //其实'='就是书中的'#'。
	InitStack2(OPND);
	SElemType MyExpression[80];	
	SElemType TempData[10];//TempData用来暂存还是字符型时期的“数字”
						   //如3*(9-7)= 中的3,9,7 只是这时还是字符型
	char * p;
	char theta, x;
	double Data, a, b;
	int i = 0;			 //数组TempData的下标
	gets(MyExpression);  //输入表达式
	p = MyExpression;    //令p指向字符型数组MyExpression
	//当读入的字符为'=' 且运算符栈OPTR的栈顶元素为'='是,结束该循环.
	//书中以'#'='#'作为结束的标志,这里以'='='='作为结束的标志
	while(*p != '=' || GetTop(OPTR) != '=')		
	{											
		if(!In(*p))		       //判断p指向的字符是否为运算符
		{					   
			TempData[i] = *p;  //接受到一个数字的时候,暂存入TempData
			p++;			   //接收下一个
			++i;               
			if(In(*p))		          //一旦下一个不是
			{
				TempData[i] = '\0';   //就立即给TempData中暂存的数字最后加上结束符
				Data=atof(TempData);  //atof  功能为:把字符串转换成浮点数  暂存入Data中 
				Push2(OPND, Data); 	  //将Data入栈
				i = 0;				  //归零
			}
		}
		else
			switch (Precede(GetTop(OPTR), *p))					
			{
				case '<':Push(OPTR,*p);	//栈顶元素优先权低
				         p++; break;
				case '=':Pop(OPTR,x);	//脱括号
						 p++; break;
				case '>':Pop(OPTR, theta);//运算符栈的栈顶元素退栈,进行Operate运算,并将运算结果入栈
						 Pop2(OPND, b); Pop2(OPND, a);
						 Push2(OPND, Operate(a, theta, b)); 
						 break;
			}

	}
	return GetTop2(OPND);
}

int main(void)
{
	printf("%g\n",EvaluateExpression());
	return 0;
	
}



运行结果截图:




【小笔记】

        if(!In(*p))            //判断p指向的字符是否为运算符  
        {                        
            TempData[i] = *p;  //接受到一个数字的时候,暂存入TempData  
            p++;               //接收下一个  
            ++i;                 
            if(In(*p))                //一旦下一个不是  
            {  
                TempData[i] = '\0';   //就立即给TempData中暂存的数字最后加上结束符  
                Data=atof(TempData);  //atof  功能为:把字符串转换成浮点数  暂存入Data中   
                Push2(OPND, Data);    //将Data入栈  
                i = 0;                //归零  
            }  
        }  


痛苦死了,起初没写这行。发现过时输入格式为   个位数(运算符)两位数=  时都没错,可一旦反过来  两位数(运算符)个位数 = 就会错。

比如12-3= 结果就等于-20;393-10= 结果就为29。

仔细研究一番,原因实在是太坑爹了。

用393-1= 举例。

gets(MyExpression);

MyExpression =

3
9
3
-
1
=
\0
\0
\0

指针p先指向第一个,即‘3’,然后将其存入TempD中,直到p指向  '-'  ,

则TempData =

3
9
3
\0
\0
\0
\0
\0
\0
\0

然后data = atof(TempData)  即data = 393;

到此时第一个数字393已经转换并且存储结束了。

接下来开始转换字符‘1’了。

但这里就出错了!

p指向1,TempData = 

1
9
3
\0
\0
\0
\0
\0
\0
\0

然后!然后就直接atof了

于是此时data = 193

再接着运算,本来应该393-1的就变成393-193了!



拯救的办法很简单!每当p指完了数字下一个指向运算符时,就紧接着在TempData 已存入的数字后面立即存上一个结束符‘\0’;

此时p应该指向‘=’了,同时TempData =

1
\0
3
\0
\0
\0
\0
\0
\0
\0

于是atof在这种情况下当然只管第一个‘\0’之前的数字了!

自然,Data = 1;


做完这个感觉不错。有时候一些错误真是出人意料啊!把自己的脑子锻炼成编译器那样,大概才能成神吧~!


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值