2018年数据结构实验之计算器(栈的应用)
数据结构老师问我们“计算器实验和学生信息管理系统实验,你们选一个吧”,好多同学都说,管理系统C语言实训做过了,做计算器吧,加减乘除,好做。
好做????我的老天爷…一会我贴出来,看代码长度就行了,/吐血!
做实验那几周,恶心的要死要死的,有两天熬到了半夜三点还在debug。
栈的操作、括号匹配、处理负数、中缀表达式转后缀表达式、计算后缀表达式…第二恶心的是老师要求“不同的错误,程序要给出不同的提示”,于是我的“判断表达式正误”的函数贼长,然后考虑各种各样的错误情况,真的吐血。
第一恶心的是老师要求“实时判错,还能实验退格”。比如这个例子:我的表达式出错了,然后程序会将第一个错误之后的都抹掉,把前面正确的输出出来,允许你对原来的表达式进行改动。即计算机输出1+7272+(8+72之后,我还能用退格键对这串字符进行删除。
众所周知,程序输出的字符是不可以删除的,只有自己从键盘输入的字符在按回车之前可以删除,然后为了实现删除程序输出的字符,借鉴室友在网上找到的小技巧,费了很大心机才做出来了…用getch函数实现实时输入、用**\b和\t实现了“删除程序输出字符”,实际上是用识别退格键**、光标前移并且输出空格进行了一个“小魔术”,欺骗了用户的眼睛。后台的数组中存储的还是原来的字符,并没有改变。
还有一个地方是半夜两点突发奇想对一个瓶颈进行了突破,在482-484行。就是如何对后台的数组进行修改,之前想了很多方法,都无济于事,那天晚上我发誓要把它写出来,半夜的代码思维真的挺强的,起码那天是这样,效率很高,灵机一动,三行代码解决问题,心里美美的!
肝了好几个深夜,最后还是做了出来。读者可以自行复制粘贴看使用效果,我个人觉得很完美了,至少从用户使用来看,可能代码上还有很多冗余的地方,望批评指正。
代码中“中缀转后缀”和“计算后缀表达式”参考了大神的博客,忘记出处了,如需要声明来源,请私信,十分感谢!
贴代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<conio.h>
#include<windows.h>
#define maxn 100
char s1[maxn];///s1存中缀表达式
char s2[maxn];///s2存后缀表达式
int system(const char*string);///清屏头文件
void setcursor(int y, int x)///光标定位头文件
{
x--;y--;
HANDLE hCon = GetStdHandle(STD_OUTPUT_HANDLE);
COORD setps;
setps.X = x; setps.Y = y;
SetConsoleCursorPosition(hCon, setps);
}
void Init()
{
printf("* * * * * * * * * * * * * * * * * * * * * * * * * * * *\n");
printf("* 欢迎使用本计算器 *\n");
printf("* 仅支持输入数字,小数点,英文小括号与四则运算符 *\n");
printf("* exit退出 clear清屏 *\n");
for(int u=1;u<15;u++)
printf("* *\n");
printf("* 作者:王晨1938*\n");
printf("* * * * * * * * * * * * * * * * * * * * * * * * * * * *\n");
setcursor(4,3);
}
typedef struct Char_stack
{
char date[maxn];
int top;
}cstack;
typedef struct Date_stack
{
double date[maxn];
int top;
}dstack;
///初始化字符栈
void Init_cstack(cstack *s)
{
s->top=-1;
}
///字符栈压栈
bool Push_cstack(cstack *s,char c)
{
if(s->top<maxn){
s->top++;
s->date[s->top]=c;
return true;
}
return false;
}
///字符栈出栈
char Pop_cstack(cstack *s)
{
char c=s->date[s->top];
s->top--;
return c;
}
///取字符栈栈顶元素
char Top_cstack(cstack *s)
{
return s->date[s->top];
}
///初始化数据栈
void Init_dstack(dstack *s)
{
s->top=-1;
}
///数据栈压栈
bool Push_dstack(dstack *s,double date)
{
if(s->top<maxn){
s->top++;
s->date[s->top]=date;
return true;
}
return false;
}
///数据栈出栈
double Pop_dstack(dstack *s)
{
double c=s->date[s->top];
s->top--;
return c;
}
///特判单独一个数
bool Spe_Jud(char *s,int t)
{
int i=0,num=0,flag=0,temp;
double sum=0,dou=0;
int sum2=0;
while(i<t){
if(s[i]<='9'&&s[i]>='0')
num++;///对数字计数
else if(s[i]=='.'){
flag++;
temp=i;///temp记录小数点出现的位置
}
i++;
}
if(num==t){///如果数字个数等于t(全部为数字)的话
printf("* 计算结果为:");
for(int k=0;k<=t-1;k++){
sum=sum*10+s[k]-'0';
}
printf("%lf\n",sum);
return false;
}
else if(flag!=0&&num+flag==t){
for(int p=0;p<=temp-1;p++)
sum=sum*10+s[p]-'0';
for(int q=temp+1;q<=t-1;q++)
dou=dou*10+s[q]-'0';
for(int j=t-temp-1;j>0;j--)
dou*=0.1;
printf("* 计算结果为:");
printf("%lf",sum+dou);
printf("\n");
return false;
}
else
return true;
}
///判断表达式正误
int Judge(char *s,int t)///-1为正确,-2为不正确也不重新输入,其他为不正确但要重新输入
{
int i=0;
int flag=0;
///判断1.1.1
for(int ii=0;ii<t;ii++){
if(flag==0&&s[ii]=='.')
flag=1;
else if(flag==1&&s[ii]=='.'){
printf("* 表达式错误!同一个数字不可以包含多个小数点\n");
return -2;
}
else if(s[ii]=='+'||s[ii]=='-'||s[ii]=='*'||s[ii]=='/'||s[ii]=='('||s[ii]==')')
flag=0;
}
///判断其他错误
while(s[i]!='\0'){
///开头只能是数字或者左括号或者负号
if(i==0){
///注意判断顺序
if(s[i]>='0'&&s[i]<='9'){
if(s[i+1]=='('){
printf("* 第%d个字符“%c”错误!“%c”后边不能直接加左括号\n",i+2,s[i+1],s[i]);
return i+1;
}
else
i++;
}
else if(s[i]=='('){
if(s[i+1]=='-'||s[i+1]=='('||s[i+1]<='9'&&s[i+1]>='0'||s[i+1]=='\0')
i++;
else{
printf("* 第%d个字符“%c”错误!“%c”后面只能是负号,左括号,数字\n",i+2,s[i+1],s[i]);
return i+1;
}
}
else if(s[i]=='-'){
if(s[i+1]=='('||s[i+1]<='9'&&s[i+1]>='0'||s[i+1]=='\0')
i++;
else{
printf("* 第%d个字符“%c”错误!“%c”后面只能是左括号或数字\n",i+2,s[i+1],s[i]);
return i+1;
}
}
else{
printf("* 第%d个字符“%c”错误!表达式只能以数字,左括号,负号开始\n",i+2,s[i+1]);
return i+1;
}
}
///结尾
else if(i==t-1){
if(s[i]<='9'&&s[i]>='0'||s[i]==')')
i++;
else{
printf("* 表达式错误!表达式只能以数字或右括号结尾\n");
return -2;
}
}
///非开头非结尾
else{
///数字后边不能是左括号
if(s[i]<='9'&&s[i]>='0'){
if(s[i+1]=='('){
printf("* 第%d个字符“%c”错误!“%c”后边不能直接加左括号\n",i+2,s[i+1],s[i]);
return i+1;
}
else
i++;
}
///加减乘后边只能是数字或者左括号
else if(s[i]=='+'||s[i]=='-'||s[i]=='*'){
if(s[i+1]=='('||s[i+1]<='9'&&s[i+1]>='0'||s[i+1]=='\0')
i++;
else{
printf("* 第%d个字符“%c”错误!“%c”后面只能是左括号或数字\n",i+2,s[i+1],s[i]);
return i+1;
}
}
///除后边只能是除0以外的数字或者左括号
else if(s[i]=='/'){
if(s[i+1]=='0'&&s[i+2]!='.'){
printf("* 第%d个字符“0”错误!“0”不能作为除数!\n",i+2);
return -2;
}
else{
if(s[i+1]=='('||s[i+1]<='9'&&s[i+1]>'0'||s[i+1]=='\0'||s[i+2]=='.')
i++;
else{
printf("* 第%d个字符“%c”错误!“%c”后面只能是左括号,0以外数字\n",i+2,s[i+1],s[i]);
return i+1;
}
}
}
///左括号后边只能是左括号、负号或数字
else if(s[i]=='('){
if(s[i+1]=='-'||s[i+1]=='('||s[i+1]<='9'&&s[i+1]>='0'||s[i+1]=='\0')
i++;
else{
printf("* 第%d个字符“%c”错误!“%c”后面只能是左括号,负号,数字\n",i+2,s[i+1],s[i]);
return i+1;
}
}
///右括号后边只能是运算符
else if(s[i]==')'){
if(s[i+1]=='+'||s[i+1]=='-'||s[i+1]=='*'||s[i+1]=='/'||s[i+1]==')'||s[i+1]=='\0')
i++;
else{
printf("* 第%d个字符“%c”错误!“%c”后面只能是运算符或右括号\n",i+2,s[i+1],s[i]);
return i+1;
}
}
///小数点后边只能是数字
else if(s[i]=='.'){
if(s[i+1]<='9'&&s[i+1]>='0'||s[i+1]=='\0')
i++;
else{
printf("* 第%d个字符“%c”错误!“%c”后只能是数字\n",i+2,s[i+1],s[i]);
return i+1;
}
}
else{
printf("* 第%d个字符“%c”错误!只支持数字、四则运算符、小数点和小括号\n",i+2,s[i+1]);
return i+1;
}
}
}
return -1;
}
///判断括号匹配
bool Is_Match(char str[],int n)
{
int i=0;
char ch;
cstack st;
Init_cstack(&st);
st.top=0;
while(i<n){
if(str[i]=='(')
Push_cstack(&st,str[i]);
else{
if(str[i]==')'){
Pop_cstack(&st);
if(st.top<0)
return false;
}
}
i++;
}
if(st.top==0)
return true;
else
return false;
}
///处理负数问题
void Handle_Negative(char *s)
{
int i=0,j=0;
while(s[i]!='\0'){
if(s[0]=='-'){///如果第一位就是符号
j=strlen(s);
while(j>0){
s[j+5]=s[j];
j--;
}
s[j++]='(';
s[j++]='0';
s[j++]='-';
s[j++]='1';
s[j++]=')';
s[j]='*';
}
if(s[i]=='('&&s[i+1]=='-'){
j=strlen(s);
while(j>i+1){
s[j+5]=s[j];
j--;
}
s[j++]='(';
s[j+1]='0';
s[j++]='-';
s[j++]='1';
s[j++]=')';
s[j]='*';
i=i+5;
}
i++;
}
}
///当前扫描运算符优先级
int Isop(char str)
{
if(str=='(')
return 6;
else if(str=='+'||str=='-')
return 2;
else if(str=='*'||str=='/')
return 4;
}
///当前扫描运算符优先级
int Inop(char str)
{
if(str=='(')
return 1;
else if(str=='+'||str=='-')
return 3;
else if(str=='*'||str=='/')
return 5;
}
///中缀表达式转后缀表达式
void Change(char *s1,char *s2)
{
int i=0,j=0;
int flag1=-1,flag2=-1;
cstack str;
Init_cstack(&str);
while(s1[i]!='\0')
{
if(flag1==0&&flag2==1)///若上次的输出为数字,上次循环扫描为字符,则表示该数字串结束,则在数字后加空格区分
{
s2[j++]=' ';
flag1=1;
}
if(s1[i]>='0'&&s1[i]<='9'||s1[i]=='.')
{
s2[j++]=s1[i];
flag2=0;
flag1=0;
}
else if(s1[i]=='+'||s1[i]=='-'||s1[i]=='*'||s1[i]=='/'||s1[i]=='(')
{
flag2=1;
if(str.top<0||Isop(s1[i])>Inop(Top_cstack(&str)))
{
Push_cstack(&str,s1[i]);
}
else
{
///当前扫描字符优先级不断与栈顶字符优先级比较,当前字符小于栈顶字符时退栈并输出
while(str.top>=0&&Isop(s1[i])<Inop(Top_cstack(&str)))
{
s2[j++]=Pop_cstack(&str);
flag1=1;
}
///当前字符优先级大于栈顶优先级或栈空时当前字符压入字符栈内
if(str.top<0||Isop(s1[i])>Inop(Top_cstack(&str)))
{
Push_cstack(&str,s1[i]);
}
}
}
else if(s1[i]==')')
{
flag2=1;
if(Top_cstack(&str)!='(') ///若括号仅包含数字则没有输出运算符
{
flag1=1;
}
while(Top_cstack(&str)!='(')
{
s2[j++]=Pop_cstack(&str);
}
Pop_cstack(&str);///将(出栈
}
i++;
}
while(str.top>=0)///将栈内剩余的运算符依次退栈输出
{
s2[j++]=Pop_cstack(&str);
}
s2[j]='\0';
}
///计算后缀表达式
double Calcu(char *s1)
{
int i=0;
int flag; ///char类型转换为double类型数据标记
double data1,data2;
double sum;
dstack ds1;
Init_dstack(&ds1);
while(s1[i]!='\0')
{
///若为运算符获取栈顶两个元素进行计算
if(s1[i]=='+'||s1[i]=='-'||s1[i]=='*'||s1[i]=='/')
{
data1=Pop_dstack(&ds1);
data2=Pop_dstack(&ds1);
if(s1[i]=='+') Push_dstack(&ds1,data2+data1);
else if(s1[i]=='-') Push_dstack(&ds1,data2-data1);
else if(s1[i]=='*') Push_dstack(&ds1,data2*data1);
else if(s1[i]=='/') Push_dstack(&ds1,data2/data1);
}
///若为数据时压栈
else
{
flag=0;
sum=0;
double divider=1;
while(s1[i]!=' '&&s1[i]!='+'&&s1[i]!='-'&&s1[i]!='*'&&s1[i]!='/')
{
if(s1[i]=='\0'){
break;
}
if(s1[i]=='.')
{
flag=1;
i++;
continue;
}
if(flag==0)
{
sum=sum*10+(double)(s1[i]-'0');
}
else
{
divider=divider*10;
sum=sum+((double)(s1[i]-'0'))/divider;
}
i++;
}
if(s1[i]=='+'||s1[i]=='-'||s1[i]=='*'||s1[i]=='/')
i--;
else if(s1[i]=='\0')
break;
Push_dstack(&ds1,sum);
}
i++;
}
return Pop_dstack(&ds1);
}
///错误表达式退格修改
void Reflash(char *s1,int i)
{
printf("* 请重新输入正确的表达式\n");
printf("* ");
for(int j=0;j<i;j++)
printf("%c",s1[j]);
char c;
int k=0;
int m;
while(1){
c=getch();
if(c=='\b'){
printf("\b \b");
k++;
}
else if(c=='\r'){
break;
}
else{
printf("%c",c);
m=k;
s1[i-m]=c;
k--;
}
}///我真是天才,这里太巧妙了
s1[i-k]='\0';
printf("\n");
}
///递归判断重新输入的表达式的正误并输出正确结果
void Recursion(char *s1)
{
int t=strlen(s1);
if(!Is_Match(s1,t)){
printf("* 表达式括号不匹配!\n");
}
else{
int r=Judge(s1,t);
if(r==-1){
if(Spe_Jud(s1,t)){
Handle_Negative(s1);
Change(s1,s2);
printf("* 计算结果为:%lf\n",Calcu(s2));
}
}
else if(r==-2){
//continue;
}
else{
Reflash(s1,r);
Recursion(s1);
}
}
}
int main()
{
Init();
int i=1;
while(1){
if(i==1){
printf(" ");
printf("请输入正确的表达式:\n");
}
else
printf("* 请输入正确的表达式:\n");
i++;
printf("* ");
scanf("%s",s1);
if(s1[0]>='a'&&s1[0]<='z'||s1[0]>='A'&&s1[0]<='Z'){
if(s1[0]=='c'&&s1[1]=='l'&&s1[2]=='e'&&s1[3]=='a'&&s1[4]=='r'){
system("cls");
Init();
}
else if(s1[0]=='e'&&s1[1]=='x'&&s1[2]=='i'&&s1[3]=='t'){
printf("* 计算器运行结束,欢迎您再次使用!\n");
break;
}
else{
printf("* 只支持数字、四则运算符、小数点和小括号\n* 退出请输入exit,清屏请输入clear\n");
}
}
else
Recursion(s1);
}
return 0;
}