一年之前 模仿了Windows自带的计算器 自己写了个简易版本 只能实现基本的加减乘数、取余、次方、开根,而且只能分步计算 ,不能直接利用运算表达式直接求解 ,当时一直没思路,这一摆就是一年,今天又翻出来琢磨了下!
分享我的几种思路:1.利用栈 把符号和数字单独分离 判断运算符的类别 取出不同的数字做不同的计算
如 1+4/2*3-5+2*(3+4)
数字栈 1 4 2 3 5 2 3 4
符号栈 + / * - + * ( + )
从不同的角度思考了好久 若表达式十分复杂 那么解析的难度将增加 如 +前面有* 或者 * 前面有 (),而()内又有*和+
则又要增加更多的栈 不容易管理
2. 直接解析字符串,按优先级顺序 边解析边计算
(1).考虑到负号和减号程序无法区分 则直接不考虑减号 凡是遇到减号 全部换成+-(加一个负数)
(2).首先去括号 把括号内的表达式计算出来 再次合并成新的字符串 若有多层括号 从最里层开始处理
(3).先计算^(次方),后计算乘除,最后计算加法
如 1+4/2*3-5+2*(3+4)
第一步 减法变加法 1+4/2*3+-5+2*(3+4)
第二步 去括号,计算结果合成新的表达式 1+4/2*3+-5+2*7
第三步 计算乘除法 1+6+-5+14
第四步 计算加法 16
按照之前的思路,我们打包成四个函数
bool isop(char op);//判断是否是运算符
void DealString(char* str);//去减号 减号变负号
void GetSimpleString(char* str);//去括号 括号内有表达式的调用GetResult() 得到结果再次合并成新的字符串后循环
bool GetSubString(char* str,char* op,double *result);//逐步化简 循环查找运算符 分离出前后两个数 根据运算符做不同的运算
double GetResult(char *str);//得到最终结果 按先次方后乘除最后加的运算顺序
那我们正式来写下代码
bool isop(char op) //判断是否是运算符 是则返回true
{
if(op=='+' || op=='*' || op=='/' || op=='^') return true;
return false;
}
void DealString(char* str));//去减号 减号变负号
{
int len=strlen(str);
int num=0;//记录插入的+数目
for(int i=len;i>0;i--)//循环查找-
{
if(str[i]=='-' && str[i-1]>='0' && str[i-1]<='9') //若为- 同时前面一个字符为数字 把+插入其中
{
for (int j=len+num;j>i;j--)//字符串后移
{
str[j]=str[j-1];
}
str[i]='+';
num++;
}
}
printf("the after deal:%s\n",str);
}
void GetSimpleString(char* str));//去括号
{
char substr[N]="";//字符子串 用于保存中间结果
char leave[N]="";//保存处理后剩余的字符串
double result=0.0;//保存中间计算结果
while(1)
{
memset(substr,0,N);
memset(leave,0,N);
char *find=strchr(str,')');//查找第一个 ) 为了优先匹配最内层括号
if(find==NULL) break;//若无括号 则退出循环函数返回
char *q=find;
while(q>=str)//循环查找 取出最内层括号中的字符串表达式
{
if(*q=='(')
{
strncpy(substr,q+1,find-(q+1));//substr 中保存括号内的字符串表达式
result=GetResult(substr);//得到中间结果
sprintf(substr,"%f",result);
if(q>str) //若(为不是整个字符串的首字符 则复制进剩余字符串中 若是首字符 则清空
strncpy(leave,str,q-str);
else
strcpy(leave,"");
strcat(leave,substr);//连接中间结果
if(*(find+1))
strcat(leave,find+1);
strcpy(str,leave);//添加后续字符串
break;
}
q--;
}
}
printf("the simple string:%s\n",str);
}
double GetResult(char *str));//得到最终结果 按先次方后乘除最后加的运算顺序
{
double result=0.0;
if(GetSubString(str,"^",&result)) return result;//计算次方
// if(GetSubString(str,'/',&result)) return result;
// if(GetSubString(str,'*',&result)) return result;
if(GetSubString(str,"*/",&result)) return result;
/*计算乘除 这里同时计算是为了防止由于改变了运算优先级而出现类似11/11*12 得到结果1/12 另一方面也是为了避免优先查找计算/ 时所造成的精度损失
*/
// if(GetSubString(str,'-',&result)) return result;
if(GetSubString(str,"+",&result)) return result;//计算加
return 0.0;
}
//逐步化简 循环查找运算符 分离出前后两个数 根据运算符做不同的运算
bool GetSubString(char* str,char* op,double *result)
{
char strnum1[N]="";
char strnum2[N]="";
char leave[N]="";
char strresu[N]="";
double num1=0.0,num2=0.0;
char *q=NULL,*p=NULL;
bool isLast=false;
while(1)
{
memset(strnum1,0,N);
memset(strnum2,0,N);
memset(leave,0,N);
memset(strresu,0,N);
char *find=str;
while(*find)//查找运算符
{
if(*find==op[0] || (op[1]!='\0' && *find==op[1])) break; 是否符合运算符
find++;
}
if (*find=='\0') break;//若没有找到运算符,说明已计算完毕
q=find;
q--;
while(q>=str)));//往前查找 分离得到第一个数
{
if(isop(*q))
{
strncpy(strnum1,q+1,find-(q+1
num1=atof(strnum1);
break;
}
else if(q==str)
{
strncpy(strnum1,q,find-q);
num1=atof(strnum1);
break;
}
q--;
}
p=find;
p++;
while(*p)//往后查找 分离得到第一个数
{
if( isop(*p))
{
strncpy(strnum2,find+1,p-(find+1));
num2=atof(strnum2);
break;
}
else if(*(p+1)=='\0')
{
strncpy(strnum2,find+1,p+1-(find+1));
num2=atof(strnum2);
p++;
break;
}
p++;
}
switch(*find)//根据查找到的符号来确定计算
{
case '+':*result=num1+num2;break;
// case '-':*result=num1-num2;break;
case '*':*result=num1*num2;break;
case '/':*result=num1/num2;break;
case '^':*result=pow(num1,num2);break;
}
sprintf(strresu,"%f",*result);//结果转换
if(q!=str)
strncpy(leave,str,q-str+1);
else
strcpy(leave,"");
strcat(leave,strresu);
if(*p!='\0')
{
strcat(leave,p);
}
if(q==str && *p=='\0') isLast=true;//若最终往前循环到首字符 往后循环到达尾字符 说明只有一级运算 则计算结果有效
strcpy(str,leave);
}
return isLast;//返回该运算是否为最终结果
}
到目前为止,解析函数已全部完毕,剩下的就是主函数调用来调试了!为了方便多次调试,我们设置了循环输入,以#结束程序
int main()
{
char str[N]="";
while(str[0]!='#')
{
memset(str,0,N);
printf("please input the string of you wang to calc['#' to quit]:\n");
scanf("%s",str);
if (str[0]!='#')
{
DealString(str);
GetSimpleString(str);
double result=GetResult(str);
printf("the result:%f\n",result);
}
}
return 0;
}
由于使用的是人工输入,因此需要添加检验运算表达式是否合格(如符号重复,括号不匹配等),我的检验函数并不完善,就不在这做说明了,你可以自行添加!
由于水平有限及时间仓促,难免有错误或遗漏之处,同时该方法执行效率和内存使用一般,如果有更好的方法或建议,欢迎补充!
运行结果测试效果图: