/*
本算法的基本思想:把一个表达式A看成是一小段连加或连减的式子,每小段单独处理,
即 A=B +- B +- B...
而每一个B项,看成是一段小连乘或除的式子,每小段单独处理,即
B=C * / C * / C...
每个C项,看成是一段连乘方的式子,即
C=D ^ D ^ D...
而每个D呢,只有三种不同的情况:一对小括号、一个函数、或者一个普通实数。
用递归函数计算A类式子的值,就可以得到整个表达式的值了。
更快速,更容易的方法是用自动机的思想,把算式的文法写出来,然后根据文法画出
相应的状态转换图(当然文法要满足一定的条件,如不能有左递归等),程序也就出来了。
*/
#define N 10000
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
char s[N+1];//存储表达式的字符串形式
int p;//扫描表达式的指针
char *lib[]={"sin", "cos", "tan", "lg", "ln", "log"}; //支持的函数列表,其中log(a,b)
double calA(); //计算完整表达式的值,在此声明一下,以用于前向调用,到最后再实现它
double calD() //计算一个数字、函数、或者小括号的值
{
char fun[4], *tmp;
int i;
double a, b;
if(s[p]=='(') //在此处理括号
{
p++; //跳过左括号
return calA(); //括号之内是完整的表达式结构
}
else if(s[p]>='a' && s[p]<='z') //出现一个字母,表示这应该是一个函数,做函数处理
{
//截取函数名
for(i=0; s[p]>='a' && s[p]<='z'; p++)
{
fun[i]=s[p];
i++;
}
fun[i]='\0';
//判断函数名,在此不做错误检查
i=0;
while(strcmp(lib[i], fun)!=0)
i++;
//执行函数计算
p++; //跳过函数名之后的左括号
switch(i)
{
case 0:
return sin(calA());
case 1:
return cos(calA());
case 2:
return tan(calA());
case 3:
return log10(calA());
case 4:
return log(calA());
case 5: //这里处理二元函数log(a,b),','分开的两个参数是完整的两个表达式
//结构
a=calA();
b=calA();
return log(b)/log(a); //对数的换底公式
}
}
else //普通实数,扫描一下就可以了
{
a=strtod(s+p, &tmp);
p=tmp-s; //用字符指针的减法得到转换中止时的位置
return a;
}
}
double calC() //计算一个乘方项的值
{
double a, b;
a=calD(); //第一个D项的值
if(s[p]=='^')
{
p++;
return pow(a, calC());//因为乘方是右结合的,所以,要先把右面的值算完才成
}
else
return a;
}
double calB() //计算一个乘除项的值
{
double a, b;
char c;
a=calC(); //第一个C项的值
while(s[p]=='*' || s[p]=='/')
{
c=s[p];
p++;
b=calC(); //下一个C项的值
if(c=='*')
a=a*b;
else
a=a/b;
}
return a;
}
double calA() //计算完整表达式的值
{
double a, b;
char c;
a=calB(); //第一个B项的值
while(s[p]=='+' || s[p]=='-')
{
c=s[p];
p++;
b=calB(); //下一个B项的值
if(c=='+')
a=a+b;
else
a=a-b;
}
if(s[p]==')' || s[p]==',') //由于括号包括起来的范围也是一个完整的表达式
//所以要做退括号处理,','是因为对数函数log用它分开两个参数
p++;
return a;
}
int main()
{
scanf("%s", s);
p=0;
printf("%lf\n", calA());
return 0;
}
表达式求值
最新推荐文章于 2021-10-12 16:51:46 发布