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位出道。刚学习算法不久,领悟也多有不足,若是有更好思路的媛友(猿友),还请不吝赐教。。。