用c语言实现一个简单的计算器(数据结构)

概要:主要以c语言为例对数据结构中写一个简易的计算器(计算整数间加减乘除括号运算)的算法进行大致讲述,细说如何去实现符号优先级的比较的函数实现。
注:由于编辑器缘故,本文内我以小写x代替了*作为乘号
栈的相关内容:https://blog.csdn.net/weixin_44579240/article/details/89255974

简易计算器

  • 整体思想:首先我们需要知道,这个算法的核心问题在于计算顺序的问题,如何让我们的程序去按照正确的顺序去计算一个算术表达式。下面举个例子 【2x2+3x(2+4)-6】 这个式子对于我们来说,计算起来不难,先计算括号内的2+4=6。然后计算最左边的2x2=4。接着是3x6=18。然后4+18=22。最后22-6=16。就算完了,这是因为我们知道括号内优先,然后是乘除,接着是加减。同等级符号计算按照从左到右的顺序。所以我们会选择性的去按照既定的顺序来读取,算术,代换,再算。但计算机是不能选择性的读取的。它遵循这从左向右的读取顺序,这时候我们需要用到栈来存储暂时不用的数据与符号以及它们的顺序。等到轮到这些运算符的时候,再将它们从栈中按顺序提取出来进行运算。
  • 运算顺序思想:首先咱来看看符号运算顺序,即括号优先,其次乘除,再者加减,同级从左向右。那么既然运算的顺序和符号息息相关,我们便需要从这些符号下手来整合出一个决定运算顺序的函数了,我们先以计算机角度来阅读下上面的式子,一步步捋出应该怎么去计算,再说使用代码实现。
  • 从左向右阅读运算:还是上面那个例子【2x2+3x(2+4)-6】 ,从左向右读取,我们先读到一个数字‘2’,然后是运算符‘x’,接着是数字‘2’,这时已经有俩个数字与一个夹在中间的运算符了,那么问题来了我们该不该运算呢?显然是无法明确知道的,因为我们需要确定这个乘号直接相接的前后运算符有没有比它更优先的存在,如果有那么它所邻接的数字应该优先参与这个优先级更高的符号的运算。好,现在我们回到式子上,我们现在所读取到的乘号它之前没有运算符,所以我们需要知道它之后这个紧接的运算符与它谁的优先级高。那么继续向后读取,读到‘+’,加号优先级比乘号小。也就是说,乘号所紧接的前后符号,优先级均低于这个乘号,那么它紧接的数字就优先与它进行运算了,即我们可以计算2x2了。有人会问了,之前我们计算的时候不是最优先计算的括号内的2+4吗,为什么到这里先算了2x2,这个顺序其实并不影响我们的计算结果,因为按照之前的运算,我们算完括号内的内容后,以结果去代替原括号,加入原计算式内进行计算,所以在从左向右阅读运算中,我们只需要在读取到括号时,将其中内容作为一个完整的运算式,最优先算出其中的值,然后取代其位置即可。
  • 符号运算顺序规则 ok,以上就是我们如何在从左向右阅读顺序之下去判断一个符号该不该运算,那么我们可以初步总结出以下规则来进行计算:
  1. 读取到括号时将括号内内容优先计算出来,然后取代原括号运算式的位置。
  2. 读取到运算符时比较其与紧接的前后两个运算符的优先级,若均优先,进行计算,否则进行搁置。
  • 改版符号运算顺序规则但是又有问题了,这样的规则无法直观的转化成代码,仅供我们理解,所以我们对规则在进行一定的细致整改,并为了让计算机知道我们的运算式何时结束,我们向符号栈栈底和运算式最后面加上‘#’作为界标,当俩界标相遇表示运算结束,(所以界标‘#’优先级最低)然后编制改良后的规则:
  1. 读取到‘#’时若其紧接的前面的算术运算符为‘#’则表示结束,否则对前接符号进行出栈运算。
  2. 读取到‘+’,‘-’,若其前接运算符为‘#’则搁置入栈,否则将其前接符号弹出运算。
  3. 读取到’x’,’/’,若其前接运算符为‘x’或’/'则将其前接符号进行出栈运算,否则搁置入栈。
  4. 读取到‘(’进行直接进行搁置入栈。
  5. 读取到‘)’,若其前接符号为‘(’则将两个符号都销毁。否则将其前接符号出栈运算。
    然后根据上面的规则,我们编写一个判断运算顺序的函数:
int Preemption(char a,char b)                           //符号优先级比较,a为当前读入,b为栈顶元素 
{
	int c;                                              //c反馈指令信息 0.结束 1.弹出 2.进栈 3.删除当前元素及栈顶元素 4.报错 
	switch(a)
	{
		case '#':if(b=='#') c=0;
				 else c=1;break;
		case '+':if(b=='#'||b=='(') c=2;
		         else c=1;break;
		case '-':if(b=='#'||b=='(') c=2;
		         else c=1;break;
		case '*':if(b=='*'||b=='/') c=1;
		         else c=2;break;
		case '/':if(b=='*'||b=='/') c=1;
		         else c=2;break;
		case '(':c=2;break;
		case ')':if(b=='(') c=3;
				 else c=1;break;
		default :c=4;break; 
	}
	return c;
} 

这个函数中我使用c作为反馈指令的媒介,函数返回值为c,而c的不同值代表不同的指令情况: 0.结束运算 1.弹出符号进行运算 2.符号搁置进栈 3.删除当前元素及栈顶元素(俩括号相遇) 4.报错(读到无法识别的字符) 。只需要在主函数中使用一个switch函数接受这个指令并对应执行即可。

  • 读取字符串中的多位十进制数字由于我们输入的算术表达式是一个字符串,所以从中直接性识别出来的只能有个位数字,而且进行储存时需要对其进行这样一个运算(例如:int a,char c,a=c-‘0’;)。如果我们读取一个数字就直接用这种方式存入数字栈中,那么我们存到的一定只能是个位数字。所以我们现在需要一块代码来负责数字的读取,确保其可以读取到多位数字。我写的代码如下:
if(*p<='9'&&*p>='0') 
		{
			a=a*10+(*p-'0');
			if(*(p+1)>'9'||*(p+1)<'0')
			{
				*p4++=a;
				a=0;
			}
			p++;
		}

其中p是指向算术表达式的指针,p4是指向数字栈的指针
ok,大致所需要注意的事项就是这些了,下面是我的完整代码:

#include <stdio.h>
#include <string.h> 
#include <stdlib.h>
int Preemption(char a,char b)                           //符号优先级比较,a为当前读入,b为栈顶元素 
{
	int c;                                              //c反馈指令信息 0.结束 1.弹出 2.进栈 3.删除当前元素及栈顶元素 4.报错 
	switch(a)
	{
		case '#':if(b=='#') c=0;
				 else c=1;break;
		case '+':if(b=='#'||b=='(') c=2;
		         else c=1;break;
		case '-':if(b=='#'||b=='(') c=2;
		         else c=1;break;
		case '*':if(b=='*'||b=='/') c=1;
		         else c=2;break;
		case '/':if(b=='*'||b=='/') c=1;
		         else c=2;break;
		case '(':c=2;break;
		case ')':if(b=='(') c=3;
				 else c=1;break;
		default :c=4;break; 
	}
	return c;
} 
int main()
{
	char str[50]={"\0"};
	char *p=str;
	double *p3,*p4,a=0,b=0;
	char *p1,*p2;
	char stack1[20];                      //符号栈 栈顶指针p2,栈底指针p1 
	double stack2[20];                       //数字栈 栈顶指针p4,栈底指针p3 
	p1=p2=stack1;
	p3=p4=stack2;
	*p2++='#';
	printf("请输入需要计算的算术表达式:");
	gets(str);
	strcat(str,"#");
	while(*p!='\0')
	{
		if(*p<='9'&&*p>='0') 
		{
			a=a*10+(*p-'0');
			if(*(p+1)>'9'||*(p+1)<'0')
			{
				*p4++=a;
				a=0;
			}
			p++;
		}
		else
		{
			switch(Preemption(*p,*(p2-1)))
			{
				case 0:
						printf("计算结果为:%lf\n",*p3);
						p++;
						break;
				case 1:
						b=*--p4;
						switch(*(--p2))
						{
							case '+':*(p4-1)=*(p4-1) + b;break;
							case '*':*(p4-1)=*(p4-1) * b;break;
							case '-':*(p4-1)=*(p4-1) - b;break;
							case '/':*(p4-1)=*(p4-1) / b;break;
						}
						break;
				case 2:
						*p2++=*p++;
						break;
				case 3:
						p++;
						p2--;
						break;
				case 4:
						printf("程序读到了无法计算的符号,出错了\n");
						p++;
						break;
			}
		 } 
	}
	return 0;
}
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页