这里的实现较为简单,也只能处理没有嵌套的表达式的情况。
相对完善的代码请见另一篇博客--完整的逆波兰式计算,修正了函数嵌套时出现的问题,调整的一定的
处理方式,使得代码更加简洁。不过大体思路与本代码基本相同。
//本代码提供的测试数据
//9.4+(3.27-1.05)*3.44+10/2.1+cos(0.52)+ln(3)/log(5)+3^2
#include
#include
#include
#include
#define MAX 100
#define MARK 65535
typedef struct {
char data[MAX];
int top;
}SqStack;
SqStack *initStack(void);
void destroyStack(SqStack *S);
void creatStack(SqStack *S);
void printStack(SqStack *S);
void changeRpn(SqStack *S,SqStack *rpn); //先形式上转化为逆波兰式
int rpnStack(SqStack *rpn,double num[MAX],int loc[MAX],int stack[MAX],char *mark); //产生一个形式逆波兰式存在 stack 中
double conduct(char mark[MAX],double num[MAX],int stack[MAX],int stacklen); //利用逆波兰式计算算式结果并返回
//处理逆波兰式的方法
//规则:从左到右遍历表达式的每个数字和符号,遇到是数字就进栈,遇到是符号,就将处于栈顶两个数字出栈,进行运算,运算结果进栈,一直到最终获得结果。
double conduct(char mark[MAX],double num[MAX],int stack[MAX],int stacklen)
{
double temp[MAX]; //用来存放中间计算过程的栈
int j = 0; //索引 temp
int p = 0; //索引 符号栈 mark
//int marklen = strlen(mark);
for(int i = 0;itop;i++) //定位出空格的位置
{
if(rpn->data[i] == ' ')
loc[lenloc++] = i;
}
//将计算需要用到的数字全都存放在 num 数组中
int count1 = 0,count2 = 0; //两个 count 用来记两个空格之间的差值,前面ver2时提到,两个空格之间有且仅有一个操作数
char a[MAX]; //用来将串转化成浮点数的临时数组 -- 记住,每次使用前都要初始化
int numlen = 0; //直接用 numlen 记出 操作数的个数
for(int i = 0;i=1) //如果两个空格之间有数字 -- 防止多个空格连续的情况
{
int p = 0; //用来往 a 数组里存字符的下标,每次用完要重置 0
while(count2data[count2] == '.' || (rpn->data[count2] >= '0'&&rpn->data[count2] <= '9')) //如果是数字或小数点 -- 可能是乘方或存操作数,需要区分
{
int k = count2;
while(rpn->data[k]>='0'&&rpn->data[k]<='9' || rpn->data[k] == '.') //先找下一个非数字和小数点的字符
k++;
if(rpn->data[k] == '^') //如果是乘方号
{
k = count2; //重置 k 到第一个数
while(rpn->data[k]>='0' && rpn->data[k] <= '9' || rpn->data[k] == '.') // ^ 号之前的是底数
a[p++] = rpn->data[k++];
double x = atof(a);
k++; //k 加 1 越过 ^ 号
memset(a,0,sizeof(a)); //重置 a 数组
p = 0;
while(rpn->data[k]>='0' && rpn->data[k] <= '9' || rpn->data[k] == '.') //^ 号后面的是指数
a[p++] = rpn->data[k++];
double y = atof(a);
if(p>0)
num[numlen++] = pow(x,y); //计算 x 的 y 次方,存入 num 中
break; //找到一个操作数,跳出当前循环 ,防止陷入重复无用的循环,增加操作数
}
else //否则就说明是纯数字,直接入 a 然后转化成浮点数存进 num 即可
{
k = count2;
while(rpn->data[k]>='0' && rpn->data[k] <= '9' || rpn->data[k] == '.') //将这一段完整的数字给找到
a[p++] = rpn->data[k++];
if(p>0) //如果 a 数组里有新存的数
num[numlen++] = atof(a);
break; //找到一个操作数,跳出当前循环 ,防止陷入重复无用的循环,增加操作数
}
}
else if(rpn->data[count2] == 'c' && rpn->data[count2+1] == 'o') //计算 cos
{
int k = count2 + 4; //k 定位到 cos 中的第一个数字
int q = 0;
memset(a,0,sizeof(a));
while(rpn->data[k] != ')') //在右括号之前的都是操作数
{
a[q++] = rpn->data[k++];
}
if(q>0)
num[numlen++] = cos(atof(a)); //计算 cos 的值,作为一个操作数存入 num ,剩下几个数学函数的计算类似
break;
}
else if(rpn->data[count2] == 's' && rpn->data[count2+1] == 'i')
{
int k = count2 + 4;
int q = 0;
memset(a,0,sizeof(a));
while(rpn->data[k] != ')')
{
a[q++] = rpn->data[k++];
}
if(q>0)
num[numlen++] = sin(atof(a));
break;
}
else if(rpn->data[count2] == 'l' && rpn->data[count2+1] == 'o')
{
int k = count2 + 4;
int q = 0;
memset(a,0,sizeof(a));
while(rpn->data[k] != ')')
{
a[q++] = rpn->data[k++];
}
if(q>0)
num[numlen++] = log10(atof(a));
break;
}
else if(rpn->data[count2] == 'l' && rpn->data[count2+1] == 'n')
{
int k = count2 + 3;
int q = 0;
memset(a,0,sizeof(a));
while(rpn->data[k] != ')')
{
a[q++] = rpn->data[k++];
}
if(q>0)
num[numlen++] = log(atof(a));
break;
}
else //如果是空格或操作符,检查下一个
count2++;
}
}
count2 = count1; //跟新前一个空格位
}
//建立形式逆波兰式的栈 stack
for(int i = 0;itop;i++)
{
if(rpn->data[i]>='0' && rpn->data[i] <= '9' || rpn->data[i] == '.') //如果是数字
{
int j = i;
while(rpn->data[j]>='0'&&rpn->data[j]<='9' || rpn->data[j] == '.') //先找下一个非数字和小数点的字符
j++;
//printf("rpn->data[%d] == %c \n",j,rpn->data[j]);
if(rpn->data[j] == '^') //最近的非数字和小数点的字符为 ^
{
j = i;
while(rpn->data[j]>='0' && rpn->data[j] <= '9' || rpn->data[j] == '.' || rpn->data[j] == '^') //将包含乘方号在内的下标同一为一个操作数下标
{
stack[u++] = count;
j++;
}
count++; //操作数个数加 1
i = j-1; //更新主循环下标,越过当前的数字 -- 值得一提的是 i 是更新为 j-1 因为在上一个循环结束后 j 走到了非操作数的位置,将 i 更新为 j-1在下一次
//循环开始时,i加 1 会正好移动到这个非操作数的位置,如果更新 i 为 j ,则会向前多进一位,可能会造成某些错误
}
else //否则是存数字
{
j = i;
while(rpn->data[j]>='0' && rpn->data[j] <= '9' || rpn->data[j] == '.') //将该浮点数占位同一为当前操作数下标
{
stack[u++] = count;
j++;
}
count++; //操作数个数加 1
i = j-1; //更新主循环下标,越过当前的数字
}
}
else if(rpn->data[i] == 'c' && rpn->data[i+1] == 'o') //将含义数学符号在内的一部分记为一个操作数下标,下面几个数学函数类似
{
int j = i;
while(rpn->data[j] != ')') //一直到后面的一个括号都需要更新 stack 为一个操作数的下标
{
stack[u++] = count;
j++;
}
stack[u++] = count;
count++;
i = j; //更新主循环下标,越过当前的数学函数 这里更新为 j 正好可以越过右括号
}
else if(rpn->data[i] == 's' && rpn->data[i+1] == 'i')
{
int j = i;
while(rpn->data[j] != ')')
{
stack[u++] = count;
j++;
}
stack[u++] = count;
count++;
i = j;
}
else if(rpn->data[i] == 'l' && rpn->data[i+1] == 'o')
{
int j = i;
while(rpn->data[j] != ')')
{
stack[u++] = count;
j++;
}
stack[u++] = count;
count++;
i = j;
}
else if(rpn->data[i] == 'l' && rpn->data[i+1] == 'n')
{
int j = i;
while(rpn->data[j] != ')')
{
stack[u++] = count;
j++;
}
stack[u++] = count;
count++;
i = j;
}
else if(rpn->data[i] == ' ') //空格则跳过
;
else //如果是操作符,stack 栈存放一个特殊的数字 MARK(符号)
stack[u++] = MARK;
}
//删除rpn数组中重复的数字类元素
//int len = rpn->top;
int i = 0;
while(i<=rpn->top)
{
if(stack[i] == -1) //如果是 -1 说明是空格需要舍弃,
{
int j;
for(j=i+1;stack[j]==-1;j++); //找到下一个不是空格 (-1) 的位置
int k = i;
while(ktop) //从当前位置开始覆盖
stack[k++] = stack[j++];
}
else if(stack[i] != MARK) //如果不是操作符,直接覆盖掉相同的数字
{
int j;
for(j=i+1;stack[j] == stack[i];j++);
int k = i+1;
while(ktop)
stack[k++] = stack[j++];
}
i++;
}
//这个循环是必要的,由于可能存在多个连续的空格,覆盖完当前的 -1 时,循环下标会下移一位,造成数字的冗余,该循环处理这些冗余数字
//相当于二次删除数组中重复元素,相关代码基本和上面相同
i = 0;
while(i<=rpn->top)
{
if(stack[i] != MARK)
{
int j;
for(j=i+1;stack[j] == stack[i];j++);
int k = i+1;
while(k<=rpn->top)
stack[k++] = stack[j++];
}
i++;
}
int stacklen = 0; //求 stack 栈的长度 -- 操作数加操作符
for(int p = 0;p<=rpn->top;p++)
if(stack[p] == MARK) //得到操作符个数
stacklen++;
stacklen = stacklen+numlen;
printf("最后得到的逆波兰式:\n");
i = 0;
int k = 0;
for(int j = 0;jdata[i] >= '0' && rpn->data[i] <= '9' || rpn->data[i] == '.' || rpn->data[i] == '^' ||
rpn->data[i] == 'c' || rpn->data[i] == 'o' || rpn->data[i] == 's' || rpn->data[i] == 'i' || rpn->data[i] == 'n' ||
rpn->data[i] == 'l' || rpn->data[i] == 'g' || rpn->data[i] == ' ' || rpn->data[i] == '(' || rpn->data[i] == ')') //是这些符号的时候原rpn->data 下标递增
i++;
printf("%c ",rpn->data[i]); //打印操作符
mark[k++] = rpn->data[i];
i++;
}
else //数字就直接打印
printf("%.2f ",num[stack[j]]);
}
return stacklen; //返回形式逆波兰式的长度
}
void changeRpn(SqStack *S,SqStack *rpn)
{
SqStack T;
T.top = -1;
int t = 0;
while(t<=S->top)
{
if(S->data[t]>='0'&&S->data[t]<='9' || S->data[t] == '.') //这里不能是数字就直接进栈了,因为乘方号在数字之间
{
int j = t;
while(S->data[j]>='0'&&S->data[j]<='9' || S->data[j] == '.') //先找下一个非数字和小数点的字符
j++;
if(S->data[j] == '^') //如果是乘方号 ,连带乘方号入栈
{
j = t;
while(S->data[j]>='0' && S->data[j] <= '9' || S->data[j] == '.' || S->data[j] == '^')
rpn->data[++rpn->top] = S->data[j++];
t = j-1;
}
else //否则按数字进栈即可
rpn->data[++rpn->top] = S->data[t];
}
else if(S->data[t]>='a' && S->data[t] <= 'z')
{
int j = t;
while(S->data[j] != ')')
{
rpn->data[++rpn->top] = S->data[j];
j++;
}
rpn->data[++rpn->top] = S->data[j];
rpn->data[++rpn->top] = ' ';
t = j;
}
else if(S->data[t]=='('||S->data[t]==')')
{
rpn->data[++rpn->top] = ' '; //当遇到符号时,说明一个数字已经结束,在逆波兰式后面加一个空格用来区分数字
if(S->data[t]=='(') //左括号直接进栈
{
T.data[++T.top] = S->data[t];
}
else if(S->data[t] == ')') //右括号则出栈到前一个左括号
{
while(T.data[T.top]!='(' && T.top>=0)
{
rpn->data[++rpn->top] = T.data[T.top--];
}
T.top--; //略去左括号
}
}
else
{
rpn->data[++rpn->top] = ' '; //当遇到符号时,说明一个数字已经结束,在逆波兰式后面加一个空格用来区分数字
if(S->data[t]=='+'||S->data[t]=='-')
{
if(T.top == -1||T.data[T.top] == '(') //加减符号,当前符号栈没有元素则直接进栈,或当前栈顶是左括号则进栈
T.data[++T.top] = S->data[t];
else //否则出栈至前一个左括号或栈空,再将其入栈
{
while(T.top>=0&&T.data[T.top]!='(')
{
rpn->data[++rpn->top] = T.data[T.top--];
}
T.data[++T.top] = S->data[t];
}
}
else //乘除直接进栈
T.data[++T.top] = S->data[t];
}
t++;
}
while(T.top>=0) //将符号栈中剩下的元素赋值给逆波兰式栈
rpn->data[++rpn->top] = T.data[T.top--];
rpn->data[++rpn->top] = ' '; //在最后加一空格做限定最后一个数的边界
}
SqStack *initStack(void)
{
SqStack *S = (SqStack *)malloc(sizeof(SqStack));
if(!S)
exit(0);
S->top = -1;
return S;
}
void destroyStack(SqStack *S)
{
free(S->data);
free(S);
}
void printStack(SqStack *S)
{
int t = 0;
while(t<=S->top)
printf("%c",S->data[t++]);
printf("\n");
}
void creatStack(SqStack *S)
{
char ch;
while(scanf("%c",&ch)==1&&ch!='\n')
S->data[++S->top] = ch;
}
int main(void)
{
freopen("逆波兰式ver3.txt","r",stdin);
SqStack *S = initStack();
creatStack(S);
printStack(S);
//double fun[MAX]; //存储计算函数的值
//conduct1(S,fun); //只是作为测试的一部,实际计算未用到
SqStack *rpn = initStack(); //先得到一个含字符的逆波兰式,后续操作都在这个式子上进行
changeRpn(S,rpn);
//printStack(rpn); //打印检查
double num[MAX]; //存储操作数
int loc[MAX]; //定位 rpn->data 中的空格 ,用来抽取操作数
int stack[MAX]; //形式逆波兰式栈,用于最后的计算
char mark[MAX]; //存放操作符 ( 再遍历 rpn->data 的到操作符太费事 )
int stacklen = rpnStack(rpn,num,loc,stack,mark); //得到 stack 形式逆波兰式,同时返回其长度,便于后续计算
printf("\n");
double result = conduct(mark,num,stack,stacklen); //计算表达式的值
printf("result = %.2f\n",result);
destroyStack(S); //销毁栈
destroyStack(rpn);
return 0;
}
//cos sin 是按弧度制计算的
//ver3 解决了带有数学函数的式子的逆波兰式的转化,利用索引数组和原始栈来共同表示这个逆波兰式
//缺陷 -- 在数学函数中可能还是含有数学函数,本段代码未处理这种嵌套的情况