要求:编写一个程序,由于求四则运算中缀表达式的值。
所谓“四则运算中缀表达式”,就是四则混合运算算式,比如9+7*(3-2),3*6-(4+8),
由于运算符号放在运算数值中间,所以被称为中缀表达式。
一、思路:
输入的字符串先用字符数组equ[1000]存放起来。扫描下去直到equ[n]=NULL为止
很自然,我们要解决如下两个大问题:
1.正确读取字符串里面的数字和运算符
比如13+95*2,你要能成功识别数字吧,能成功分理出数字13,95,2和运算符+和*
这个问题比较简单,编写函数digits来实现。细节不讲了
2.按照优先级进行运算
首先分成两小类:括号优先级,运算符优先级。其中括号优先级十分简单,说这个
(1)括号优先级
假设我们已经编写了一个函数operation,能正确计算算式,下面有算式8-(7+6*2)+7*5
算式 8-(7+6*2)+7*5
operation(0):8-(7+6*2)+7*5
↑
①扫描到'('处,operation递归,调用一新的operation(1)函数;继续扫描字符串并计算
operation(1):7+6*2)+7*5
②扫描到')'处:此时operation(1)计算完7+6*2的内容,计算结果=7+6*2=19
检测到')',operation(1)执行完成,返回运算结果给operation(0),得到:
operation(0):8-19+7*5
↑
(2)运算符号优先级
这是这个计算器的核心算法。
想像一下,如果算式全都是相同优先级的运算符号,比如加和减,完全可以从左一路运算到右
算式7+8-9+4,边扫描边根据符号加减,是没有问题的,7+8=15,15-9=6,6+4=10
但是优先级不同了,多了个乘或除,再这样运算就会出错了
把算式改成7+8*9+4,再根据符号从左到右运算,就会变成7+8=15,15*9=145,145+4=149
既然如此,我们干脆先把数值和符号都先分别放到链栈p和q存起来
为了方便出栈,链栈p和q要为 栈顶->栈底
压栈运算符优先级=in_push,q栈顶运算符优先级=top
①in_push<top,说明前一步运算的优先级大;所以q栈的元素要出栈,同时弹出两个数字进行运算,运算后再压回去。通过这步运算,7+9*6-4可以化成7+54-4
②in_push=top,我们也不能直接压栈,而是先把同级运算符先弹出运算,直到满足③或q栈空才能再压入。
为什么?因为算式是从左往右运算的,如果同级运算符直接压栈,到时候扫描完成,弹栈运算的时候,相当于从右往左计算,如下图:
8-7+6
先压再运算(错误):
由于同级,扫描完字符串后可以得到
p:6→7→8 q:+→-
先弹出6和7及运算符+,计算6+7=15,压栈得:
p:15→8 q:-
再弹出15和8,及符号-,计算得8-15=-7
先运算再压栈(正确):
检测到‘+’时,先计算8-7=1,然后1,+分别压入p和q栈
字符串扫描完成,有:p:6→1, q:+
弹出并运算得6+1=7
③in_push>top,直接压栈,这个不必多说了
具体思路就讲完了,当然,除了基本的四则运算,我们也可以添加运算,如取余,幂次,开方,对数,三角函数等等。
只要设置好相应的运算优先级即可
二、示例演示
以算式9+7*8-3为例,演示一遍计算器具体操作:
算式:9+7*8-13 p栈:数值 q栈:操作符
①9+7*8-13 9入栈
↑ p:9 q:null
②9+7*8-13 ‘+’入栈
↑ p:9 q:+
③9+7*8-13 7入栈
↑ p:7→9 q:+
④9+7*8-13 ‘*’优先级大于q栈顶元素‘+’,‘+’入栈
↑ p:7→9 q:*→+
⑤9+7*8-13 8入栈
↑ p:8→7→9 q:*→+
⑥9+7*8-13 ‘-’优先级小于‘*’,弹出‘*’,及p栈顶两数8,7计算。得8*7=56,将56压栈
↑ p:56→9 q:+
⑦9+7*8-13 ‘-’优先级等于‘+’,弹出‘+’,及p栈顶两数56,9计算。得56+9=65,将65压栈
↑ p:65 q:
⑧9+7*8-13 q栈空,‘-’压栈
↑ p:65 q:-
⑨9+7*8-13 13入栈
↑ p:13→65 q:-
⑩9+7*8-13 字符串扫描完成,将q里面的运算符一次弹出运算(每次q弹1个p弹2个)
p弹出13,65 q弹出‘-’号,计算65-13=52
压栈:p:52
结果:p栈的值52即为最终答案
三、代码
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<windows.h>
#define newp (node*)malloc(sizeof(node))
typedef struct stack{
double val;//存放运算数值
char ope;//存放运算符
int pri;//运算符优先级
struct stack *next;
}node; //建立栈类型
void gotoxy(int x, int y){
COORD pos;//表示一个字符在控制台屏幕上的坐标
pos.X = x;
pos.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);//是API中定位光标位置的函数。
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cci;
GetConsoleCursorInfo(hOut,&cci);//获取光标信息
cci.bVisible = FALSE;//隐藏光标
SetConsoleCursorInfo(hOut,&cci);//设置光标信息
}//光标控制模块
int digits(char equ[1000],int scan[1]){
int i=0,n,num=0;
while(equ[scan[0]+i]>='0' && equ[scan[0]+i]<='9'){
++i;
}
n=i;
while(i>0){
num+=(equ[scan[0]+(n-i)]-48)*pow(10,i-1);
--i;
}
scan[0]+=n-1;
return num;
}//数值提取器
bool priority(int push,int top){//压栈操作符和s_ope栈顶操作符的优先级
if(push>top){
return 1;}
return 0;
}//优先级判断器
void cal(node *s,char ope){
gotoxy(0,22);
if(ope=='+') s->next->val+=s->val;
if(ope=='-') s->next->val-=s->val;
if(ope=='*') s->next->val*=s->val;
if(ope=='/'){if(s->val!=0)s->next->val=s->next->val/s->val;
else printf("[运算错误:不可以除0!]");}
if(ope=='%'){if(s->val==0) printf("[运算错误:不可以对0取模!]");
if((int)s->val+(int)s->next->val!=s->val+s->next->val) printf("[运算错误:小数间不可取余!]");
else s->next->val=(int)s->next->val%(int)s->val;
}
if(ope=='^'){s->next->val=pow(s->next->val,s->val);}
if(ope=='@'){if(s->val<0)printf("[运算错误:负数无法开根!]");
else s->val=sqrt(s->val);printf("(%d %d)",s->val,sqrt(s->val));
}
} //运算器
int leve(char t){
if(t=='+' || t=='-') return 1;
if(t=='*' || t=='/' || t=='%') return 2;
if(t=='^' || t=='@') return 3;
}
double operation(char equ[1000],int scan[1]){
char oper[]={'+','-','*','/','^','@','%'};
++scan[0];
node *s_val=newp,*s_ope=newp;//数值链栈,操作符链栈
s_val->next=NULL,s_ope->next=NULL;//下一个节点设为空
s_val->val=0;s_ope->ope='#';s_ope->pri=-1;//设置栈底元素
while(equ[scan[0]]!=NULL && scan[0]<1000 && equ[scan[0]]!=')'){
char e=equ[scan[0]];
if(e=='('){
node *s_n=newp;s_n->next=s_val;s_n->val=operation(equ,scan);s_val=s_n;
}
if(e>='0' && e<='9'){
node *s_n=newp;s_n->next=s_val;s_n->val=digits(equ,scan);s_val=s_n;
}
int key=0;
for(int i=0;i<7;++i){
if(e==oper[i]){key=1;}
}
if(key==1){
int Leve=leve(equ[scan[0]]);
while(priority(Leve,s_ope->pri)==0){
cal(s_val,s_ope->ope); s_val=s_val->next;s_ope=s_ope->next;
}//若压栈运算符优先级不大于栈顶元素,一直出栈,并运算,直到能压入栈为止
node *s_o=newp;s_o->next=s_ope;s_o->ope=equ[scan[0]];s_o->pri=Leve;s_ope=s_o;
}
++scan[0];
}
while(s_ope->ope!='#'){
cal(s_val,s_ope->ope);s_val=s_val->next;s_ope=s_ope->next;
}
return s_val->val;
}//字符翻译兼运行中心
int main(){
int move=0,scan[1];
printf("\n*****************************************************************\n");
printf("中缀计算器 \n");
printf("1.运算符中间必须有运算数值或括号\n");
printf("2.注意算式输入的正确性\n");
printf("3.运算范围:+-2^31\n");
printf("4.输入字符串的字符数量要小于1000\n");
printf("5.仅支持非负整数输入!小数用整数相除的方式输入,负数用0-n的方式输入\n");
printf("小数1.72表示为172/100,负数-2.7表示为0-27/10\n");
printf("--------------------------------------------------------------------\n");
printf("运算符优先级\n");
printf("1级:+(加) -(减)\n2级:*(乘) /(除) %%(取余)\n3级:^(幂) @(开方)\n最高级:左括号‘(’+ 右括号‘)’\n");
printf("*******************************************************************\n\n");
while(move==0){
gotoxy(0,20);
printf("输入算式:");
char equ[1000]={0};
scan[0]=-1;
scanf("%s",&equ);
double total=operation(equ,scan);
gotoxy(0,21);printf("\n结果:%lf 输入'0'继续",total);scanf("%d",&move);
gotoxy(0,20); printf("输入算式: ");
gotoxy(0,22);printf(" ");
}
return 0;
}