(C语言)静态栈编写简单计算器(感悟与详细解析)

C语言数据结构静态栈——计算器的实现

下面我来详细的解释一下我编写的计算器代码。。。。
有必要先说明一下的是,此代码中含有两个栈:数据栈和算符栈

数据栈:存放double型数字;
算符栈:存放char型运算符(加、减、乘、除、括号)和起止标志符(#);

#define maxsize 30
/*数据栈*/
typedef struct Stack_F
{
	double data[maxsize];
	int top; //栈顶指针
}num;   //number 数字 

/*算符栈*/
typedef struct Stack_C
{
	char data[maxsize];
	int top; //栈顶指针
}sym;   //symbol 符号

关于栈的操作函数有如下几个:

/*数据栈初始化*/
void Initstack_num(num& N)
{
	N.top = 0;
}

/*算符栈初始化*/
void Initstack_sym(sym& S)
{
	S.top = 0;
}

/*数据栈压栈*/
void Pushstack_num(num& N, double e)
{
	N.data[N.top] = e;
	N.top++;
}

/*算符栈压栈*/
void Pushstack_sym(sym& S, char e)
{
	S.data[S.top] = e;
	S.top++;
}

/*数据栈出栈*/
double Popstack_num(num& N)
{
	N.top--;
	return N.data[N.top];
}

/*算符栈出栈*/
char Popstack_sym(sym& S)
{
	S.top--;
	return S.data[S.top];
}

接下来我们步入正题:
使用 gets_s() 在控制台上输入 字符串(算式),调用数据栈和算符栈初始化函数,再调用(分类函数)sort() ,把算式传到分类函数中进行数字和运算符的分类。
话说:为啥会用到 gets_s() 而不是用 get();
(⊙﹏⊙),我也想用 get();但是 vs2019 编译器不允许········,度娘肯定知道啥子原因!!。。

/*主函数*/
int main()
{
	void sort(num & N, sym & S, char str[]);
	void Initstack_num(num & N);
	void Initstack_sym(sym & S);
	char str[maxsize];//初始化字符串大小
	num N;
	sym S;
	printf("***********************************************************************************************************************\n");
	printf("*****************************************************栈式简单计算器****************************************************\n");
	printf("\n提示:输入的算式以 ‘#’ 符号为开始符,以  ‘#’ 符号为结束符,在算式中可以包含的运算有‘+’‘-’‘*’‘/’‘(’‘)’,输入完毕之后,直接回车即可\n ");
	printf("\n\n注意:运算符必须在英式状态下输入!!!");
	printf("\n\n\n输入算式示例:       #1+2#   ");
	printf("\n\n请输入所需要计算的算式:           ");
	gets_s(str);//输入字符串
	Initstack_num(N);
	Initstack_sym(S);
	sort(N, S, str);
	return 0;
}


下面才是计算器代码中的精华部分 ——sort()函数(对输入的算式进行分类),在进行解释之前呢,先把思路说一下:
1.凡是遇到数字直接压入数据栈。
2.
a、当遇到运算符(加、减、乘、除),先判断算符栈中是否为空:
若为空:二话不说直接把它压入算符栈。
若不为空:比较运算符优先级之后,判断是否压入算符栈;
b、 当遇到运算符(右括号),直接压入算符栈,因为左括号的优先级比加减乘除都高。
c、当遇到运算符(左括号),直接取出算符栈的运算符,直到匹配到左括号为止。
d、当遇到标志符(#),如果是第一个:直接压入算符栈,因为第一个#是标志的算式的开始,它必须进入算符栈;如果是最后一个,直接取出算符栈中的符号,直到匹配到 # 为止。

思路就是这个思路,但为了方便理解第1条中怎么把数字字符转换成数字,并将其压入数据栈,咱么先来认真分析 sort() 函数中的一部分代码:

int i = 0, j = 0,m=0;
char Buff[maxsize];//预存数字字符串
if (str[i] >= '0' && str[i] <= '9')//如果该字符为数字字符,则执行如下代码
		{

			while (str[i] >= '0' && str[i] <= '9')//如果该字符为数字字符,则进入while循环
			{
				Buff[j] = str[i];//把str[i]字符赋值给Buff[maxsize]数组
				j++;
				i++;
			}
			/*
			当上面的while循环退出之后,Buff数组中是存有数字字符的,用d指针指向数组下标为m时的位置,调用atof函数,
			从m位置(m初始化为0)开始,直到遇见数组中的‘\0’字符时,把这之间的数字字符转换成数字
			*/
			d = &Buff[m];
			c = atof(d);//把Buff数组中的数字字符串转换为浮点型数据
			m = j;
			Pushstack_num(N, c);//把数字压入数据栈
		}

可能有一些媛媛(猿猿)不明白一个问题:
d = &Buff[m];
c = atof(d);
m = j;

这三句话啥子意思?为啥子这样写?
对于这个问题憨憨的我也是改了又改之后,才确定的这样下笔的。
之前呢,我是这样写的:
d = &Buff[j];
c = atof(d);
j=0;

为了能更好的解决媛媛(猿猿)的疑惑,咱们来画图比较一下说明:
先看错误示例的代码片段:(算式举例: #10+2#
本猿写了个代码执行此算式来检测一下数字在数据栈中的存储情况:
结果如下:
在这里插入图片描述
可以看见。明明输入的算式是: 10+2 ,按理说数据栈中的数字应该是:
10.000000 2.000000,显然:结果并不再你的意料之中哇~

既然如此咱们来分析一下,上面所提到的错误代码:

在这里插入图片描述
在这里插入图片描述
这是错误代码片段的分析图:可以看到,这个代码片段的确可以将数字字符转换为数字,但是第一次转化的时候是正确,但是第二次确是错误的。原因就是,每一次虽然把数字字符存储到了Buff数组中,但是从下一次判断是“数字字符”开始,指针d再次从Buff[0]处进行扫描,的确把Buff数组中的第一个数字字符覆盖掉了,但是数组中第二个Buff[[1]还是存在的,故而导致第二次中压入数据栈的数字变成了20,而不是2.。。。。

好了,再看正确示例的代码片段:
先来检测一下数字在数据栈中的存储情况:
在这里插入图片描述
啊哈,这一次的结果终于和猜想一样的了,但是为啥子呢:
咱们分析一下,看原理图:
在这里插入图片描述
在这里插入图片描述
终于终于。。。分析完了,这个过程本猿就不在用文字讲述了,看图应该是看得懂的哇!!。 不信??。继续看。。。我相信你是一只聪明的猿猿(媛媛)呢!
讲到这里。猿猿(媛媛)们是否懂了呢?

对了,本猿这里有一张优先级对照表,猿猿(媛媛)可以参考一下:
在这里插入图片描述
好了,废话不多说,接下来上 sort() 函数代码:
里面每一部分都是有注释的,所以,我就不再用文字啰嗦了呢~~~~

/*分类函数*/
void sort(num& N, sym& S, char str[])
{
	void Pushstack_num(num & N, double e);
	void Pushstack_sym(sym & S, char e);
	double Popstack_num(num & N);
	char Popstack_sym(sym & S);
	double calculate(double a, double b, char n);
	int i = 0, j = 0,m=0;
	double a, b;//接收数据栈返回值
	double c;//暂时存储数字字符转换成浮点型数值
	char* d;//声明d指针,是为了后续调用atof函数:   double    __cdecl atof   (_In_z_ char const* _String);
	double f;//接收运算函数的返回值,将其压入数据栈
	char k;//接收str数组中一个临时字符
	char n;//接收算符栈的返回值
	double value;//最终算式的结果值
	char Buff[maxsize];//预存数字字符串
	while (str[i] != '\0')//判断第i个字符是否为截至字符,如果是,跳出while循环,如果不是,进入while循环
	{
		if (str[i] >= '0' && str[i] <= '9')//如果该字符为数字字符,则执行如下代码
		{

			while (str[i] >= '0' && str[i] <= '9')//如果该字符为数字字符,则进入while循环
			{
				Buff[j] = str[i];//把str[i]字符赋值给Buff[maxsize]数组
				j++;
				i++;
			}
			/*
			当上面的while循环退出之后,Buff数组中是存有数字字符的,用d指针指向数组下标为m时的位置,调用atof函数,
			从m位置(m初始化为0)开始,直到遇见数组中的‘\0’字符时,把这之间的数字字符转换成数字
			*/
			d = &Buff[m];
			c = atof(d);//把Buff数组中的数字字符串转换为浮点型数据
			m = j;
			Pushstack_num(N, c);//把数字压入数据栈
		}

		else //如果该字符是运算符,则执行如下代码
		{
			/*遇到开始标志的‘#’,直接进入算符栈*/
			if (str[i] == '#' && i == 0)//str[i]=='#'&&i==0   是为了区分‘#’是算式的开始标志,还是算式的结束标志
			{
				k = str[i];
				Pushstack_sym(S, k);
			}
			/*遇到左括号‘(’字符,直接进入算符栈*/
			else if (str[i] == '(')
			{
				k = str[i];
				Pushstack_sym(S, k);
			}
			/*
			‘+’‘-’属于同等级运算符,不需要比较
			*/
			else if (str[i] == '+' || str[i] == '-')//如果遇到字符是‘+’或者‘-’,在算符栈中若只含有‘#’‘(’,那么该运算字符直接入算符栈
			{

				k = str[i];
				n = Popstack_sym(S);
				if (n == '#' || n == '(')
				{
					Pushstack_sym(S, n);
					Pushstack_sym(S, k);
				}
				else
				{
					while (n == '*' || n == '/' || n == '+' || n == '-')//如果栈顶运算符是‘*’'-''+'‘/’,那么就先取出数据栈中的两个数字,调用运算函数,进行计算
					{
						/*取出数据栈中两个数字,调用运算函数,传入a,b参数,进行计算,再将返回值放入数据栈*/
						a = Popstack_num(N);
						b = Popstack_num(N);  
						f = calculate(a, b, n);
						Pushstack_num(N, f);
						n = Popstack_sym(S);//再次取出算符栈栈顶字符,判断n是否为 ‘+’‘-’‘*’‘/’
		            }
					/*如果取出的是字符‘#’或‘(’时,跳出while循环,并把‘#’或‘(’字符再压入算符栈,同时字符k也压入算符栈*/
					Pushstack_sym(S, n);
					Pushstack_sym(S, k);
				}

			}
			else if (str[i] == '*' || str[i] == '/')//如果检测到字符为‘*’‘/’时,执行以下操作
			{
				k = str[i];
				n = Popstack_sym(S);//取出算符栈中的一个字符
				if (n == '#' || n == '+' || n == '-' || n == '(')//对该字符进行判断:‘*’‘/’的优先级大于‘#’‘+’‘-’‘(’,直接压入算符栈
				{
					Pushstack_sym(S, n);
					Pushstack_sym(S, k);
				}
				else//
				{
					while (n == '*' || n == '/')//若是遇到同等级的运算符则需要将进行以下操作
					{
						/*取出数据栈中两个数字,调用运算函数,传入a,b参数,进行计算,再将返回值放入数据栈*/
						a = Popstack_num(N);
						b = Popstack_num(N);
						f = calculate(a, b, n);
						Pushstack_num(N, f);
						
						n = Popstack_sym(S);//再次取出算符栈中的栈顶字符,判断是否为’*‘’/‘字符,如果不是跳出循环,如果是继续循环
					}
					/*如果取出的是字符‘#’或‘(’时,跳出while循环,并把‘#’或‘(’字符再压入算符栈,字符k也压入算符栈*/
					Pushstack_sym(S, n);
					Pushstack_sym(S, k);
				}
			}
			/*遇到‘)’字符时,不需要将其压入算符栈,此时需将算符栈中的运算字符取出,调用运算函数,直到取到‘(’字符为止。*/
			else if (str[i] == ')')
			{
				int s = 0;
				n = Popstack_sym(S);
				while (n != '(')
				{
					a = Popstack_num(N);
					b = Popstack_num(N);
					f = calculate(a, b, n);
					Pushstack_num(N, f);
					n = Popstack_sym(S);//如果在这一步取出的栈顶字符是‘(’,除了会退出循环以外,‘(’字符也不需要在进入算符栈中了,直接丢即可
				}

			}
			/*
			 在把输入的所有字符进行分栈压入之后,当检测到‘#’结束字符之后时,是不需要在把‘#’字符压入算符栈中,
			 这时需要退出栈分配阶段,取数据栈和算符栈中元素进行最后的计算,直至遇到算符栈栈底中的‘#’字符时,输出数据栈中的计算结果结果
			*/
			else if (str[i] == '#' && i != 0)
			{
				n = Popstack_sym(S);
				while (n != '#')
				{
					a = Popstack_num(N);
					b = Popstack_num(N);
					f = calculate(a, b, n);
					Pushstack_num(N, f);
					n = Popstack_sym(S);
				}
				value = Popstack_num(N);
				printf("该算式的计算结果为:               %0.2lf\n", value);

			}

			i++;
		}

	}


}

接下来是运算函数 calculate() :

/*运算函数*/
double calculate(double a, double b, char n)
{
	switch (n)
	{
	case '+':   return  b + a;
	case '-':   return  b - a;
	case '*':   return  b * a;
	case '/':   return  b / a;
	default:   exit(0);//如果接收的不是:‘+’‘-’‘*’‘/’时,直接退出程序,当然这种情况是不存在的!
	}
}

对于switch选择语句中的 default,我刚开始的时候在后面写的是 break;
在这里插入图片描述
但是呢:编译器有一个提示:
在这里插入图片描述
虽然这个提示并不影响程序的运行,但是为了程序能够尽量的减少瑕疵:
本猿就改成了:在这里插入图片描述

好了好了,终于一段一段的说完了,
那么是时候供上计算器的完整代码了:

/*静态栈式计算器*/
#include<stdio.h>
#include<stdlib.h>

#define maxsize 30

/*数据栈*/
typedef struct Stack_F
{
	double data[maxsize];
	int top; //栈顶指针
}num;   //number 数字 

/*算符栈*/
typedef struct Stack_C
{
	char data[maxsize];
	int top; //栈顶指针
}sym;   //symbol 符号

/*数据栈初始化*/
void Initstack_num(num& N)
{
	N.top = 0;
}

/*算符栈初始化*/
void Initstack_sym(sym& S)
{
	S.top = 0;
}

/*数据栈压栈*/
void Pushstack_num(num& N, double e)
{
	N.data[N.top] = e;
	N.top++;
}

/*算符栈压栈*/
void Pushstack_sym(sym& S, char e)
{
	S.data[S.top] = e;
	S.top++;
}

/*数据栈出栈*/
double Popstack_num(num& N)
{
	N.top--;
	return N.data[N.top];
}

/*算符栈出栈*/
char Popstack_sym(sym& S)
{
	S.top--;
	return S.data[S.top];
}

/*分类函数*/
void sort(num& N, sym& S, char str[])
{
	void Pushstack_num(num & N, double e);
	void Pushstack_sym(sym & S, char e);
	double Popstack_num(num & N);
	char Popstack_sym(sym & S);
	double calculate(double a, double b, char n);
	int i = 0, j = 0,m=0;
	double a, b;//接收数据栈返回值
	double c;//暂时存储数字字符转换成浮点型数值
	char* d;//声明d指针,是为了后续调用atof函数:   double    __cdecl atof   (_In_z_ char const* _String);
	double f;//接收运算函数的返回值,将其压入数据栈
	char k;//接收str数组中一个临时字符
	char n;//接收算符栈的返回值
	double value;//最终算式的结果值
	char Buff[maxsize];//预存数字字符串
	while (str[i] != '\0')//判断第i个字符是否为截至字符,如果是,跳出while循环,如果不是,进入while循环
	{
		if (str[i] >= '0' && str[i] <= '9')//如果该字符为数字字符,则执行如下代码
		{

			while (str[i] >= '0' && str[i] <= '9')//如果该字符为数字字符,则进入while循环
			{
				Buff[j] = str[i];//把str[i]字符赋值给Buff[maxsize]数组
				j++;
				i++;
			}
			/*
			当上面的while循环退出之后,Buff数组中是存有数字字符的,用d指针指向数组下标为m时的位置,调用atof函数,
			从m位置(m初始化为0)开始,直到遇见数组中的‘\0’字符时,把这之间的数字字符转换成数字
			*/
			d = &Buff[m];
			c = atof(d);//把Buff数组中的数字字符串转换为浮点型数据
			m=j;
			Pushstack_num(N, c);//把数字压入数据栈
		}

		else //如果该字符是运算符,则执行如下代码
		{
			/*遇到开始标志的‘#’,直接进入算符栈*/
			if (str[i] == '#' && i == 0)//str[i]=='#'&&i==0   是为了区分‘#’是算式的开始标志,还是算式的结束标志
			{
				k = str[i];
				Pushstack_sym(S, k);
			}
			/*遇到左括号‘(’字符,直接进入算符栈*/
			else if (str[i] == '(')
			{
				k = str[i];
				Pushstack_sym(S, k);
			}
			/*
			‘+’‘-’属于同等级运算符,不需要比较
			*/
			else if (str[i] == '+' || str[i] == '-')//如果遇到字符是‘+’或者‘-’,在算符栈中若只含有‘#’‘(’,那么该运算字符直接入算符栈
			{

				k = str[i];
				n = Popstack_sym(S);
				if (n == '#' || n == '(')
				{
					Pushstack_sym(S, n);
					Pushstack_sym(S, k);
				}
				else
				{
					while (n == '*' || n == '/' || n == '+' || n == '-')//如果栈顶运算符是‘*’'-''+'‘/’,那么就先取出数据栈中的两个数字,调用运算函数,进行计算
					{
						/*取出数据栈中两个数字,调用运算函数,传入a,b参数,进行计算,再将返回值放入数据栈*/
						a = Popstack_num(N);
						b = Popstack_num(N);  
						f = calculate(a, b, n);
						Pushstack_num(N, f);
						n = Popstack_sym(S);//再次取出算符栈栈顶字符,判断n是否为 ‘+’‘-’‘*’‘/’
		            }
					/*如果取出的是字符‘#’或‘(’时,跳出while循环,并把‘#’或‘(’字符再压入算符栈,同时字符k也压入算符栈*/
					Pushstack_sym(S, n);
					Pushstack_sym(S, k);
				}

			}
			else if (str[i] == '*' || str[i] == '/')//如果检测到字符为‘*’‘/’时,执行以下操作
			{
				k = str[i];
				n = Popstack_sym(S);//取出算符栈中的一个字符
				if (n == '#' || n == '+' || n == '-' || n == '(')//对该字符进行判断:‘*’‘/’的优先级大于‘#’‘+’‘-’‘(’,直接压入算符栈
				{
					Pushstack_sym(S, n);
					Pushstack_sym(S, k);
				}
				else//
				{
					while (n == '*' || n == '/')//若是遇到同等级的运算符则需要将进行以下操作
					{
						/*取出数据栈中两个数字,调用运算函数,传入a,b参数,进行计算,再将返回值放入数据栈*/
						a = Popstack_num(N);
						b = Popstack_num(N);
						f = calculate(a, b, n);
						Pushstack_num(N, f);
						
						n = Popstack_sym(S);//再次取出算符栈中的栈顶字符,判断是否为’*‘’/‘字符,如果不是跳出循环,如果是继续循环
					}
					/*如果取出的是字符‘#’或‘(’时,跳出while循环,并把‘#’或‘(’字符再压入算符栈,字符k也压入算符栈*/
					Pushstack_sym(S, n);
					Pushstack_sym(S, k);
				}
			}
			/*遇到‘)’字符时,不需要将其压入算符栈,此时需将算符栈中的运算字符取出,调用运算函数,直到取到‘(’字符为止。*/
			else if (str[i] == ')')
			{
				int s = 0;
				n = Popstack_sym(S);
				while (n != '(')
				{
					a = Popstack_num(N);
					b = Popstack_num(N);
					f = calculate(a, b, n);
					Pushstack_num(N, f);
					n = Popstack_sym(S);//如果在这一步取出的栈顶字符是‘(’,除了会退出循环以外,‘(’字符也不需要在进入算符栈中了,直接丢即可
				}

			}
			/*
			 在把输入的所有字符进行分栈压入之后,当检测到‘#’结束字符之后时,是不需要在把‘#’字符压入算符栈中,
			 这时需要退出栈分配阶段,取数据栈和算符栈中元素进行最后的计算,直至遇到算符栈栈底中的‘#’字符时,输出数据栈中的计算结果结果
			*/
			else if (str[i] == '#' && i != 0)
			{
				n = Popstack_sym(S);
				while (n != '#')
				{
					a = Popstack_num(N);
					b = Popstack_num(N);
					f = calculate(a, b, n);
					Pushstack_num(N, f);
					n = Popstack_sym(S);
				}
				value = Popstack_num(N);
				printf("该算式的计算结果为:               %0.2lf\n", value);

			}

			i++;
		}

	}


}

/*运算函数*/
double calculate(double a, double b, char n)
{
	switch (n)
	{
	case '+':   return  b + a;
	case '-':   return  b - a;
	case '*':   return  b * a;
	case '/':   return  b / a;
	default:   exit(0);//如果接收的不是:‘+’‘-’‘*’‘/’时,直接退出程序,当然这种情况是不存在的!
	}
}

/*主函数*/
int main()
{
	void sort(num & N, sym & S, char str[]);
	void Initstack_num(num & N);
	void Initstack_sym(sym & S);
	char str[maxsize];//初始化字符串大小
	num N;
	sym S;
	printf("***********************************************************************************************************************\n");
	printf("*****************************************************栈式简单计算器****************************************************\n");
	printf("\n提示:输入的算式以 ‘#’ 符号为开始符,以  ‘#’ 符号为结束符,在算式中可以包含的运算有‘+’‘-’‘*’‘/’‘(’‘)’,输入完毕之后,直接回车即可\n ");
	printf("\n\n注意:运算符必须在英式状态下输入!!!");
	printf("\n\n\n输入算式示例:       #1+2#   ");
	printf("\n\n请输入所需要计算的算式:           ");
	gets_s(str);//输入字符串
	Initstack_num(N);
	Initstack_sym(S);
	sort(N, S, str);
	return 0;
}


另外呢,在程序中用到了 <stdlib.h>中的 atof函数。在此呢,本猿再分享给各位猿猿(媛媛)一个东东:(C语言函数大全)
链接:https://pan.baidu.com/s/1op0-NiSWCuJ3OaCkC1U9Wg
提取码:ogqd

本猿C位出道。刚学习算法不久,领悟也多有不足,若是有更好思路的媛友(猿友),还请不吝赐教。。。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值