用C语言写一个计算器

2022-10-24 12:57:34

用C语言写一个计算器,除了四则混合运算之外,还支持三角函数和绝对值等函数。

PS E:\Code\PL\calc>.\a.exeabs(3*5-4^2)abs(3*5-4^2)=1.00000025-7+6*(4-5)25-7+6*(4-5)=12.000000

在计算器中,至少包含两类变量,即数字和运算符。例如,如果希望实现 a + b × ( c − d ) a+b\times (c-d)a+b×(cd)这样一个简单的功能,要求编译器可以识别出 + , × , − , ( , ) +,\times,-,(,)+,×,,(,)这五个符号,并理清彼此的计算顺序,最后生成一棵语法树,然后实现输出。

1. 加减法运算

万事开头难,所以我们选择一个简单到无脑的开头。首先,我们考虑实现 a + b a+ba+b这样简单的两数运算,即如下所示,十分简单且无脑。

voiddouCalc(){while(1){double i, j, s;char k;scanf("%lf%c%lf",&i,&k,&j);switch(k){case'+':
            s= i+j;break;case'-':
            s= i-j;break;case'*':
            s= i*j;break;case'/':
            s= i/j;break;default:break;}printf("%lf\n", s);}}

然后,我们考虑,如何实现一个连加器,旨在解决 a + b + c + . . . a+b+c+...a+b+c+...的计算问题。这里虽然不涉及到运算次序,但仍旧需要处理多个不确定个数的变量,所以我们不再可以直接用类似scanf("%lf%c%lf", &i, &k, &j);的方案来实现数据的输入,而必须建立一个链表来存储变量。

C语言输入输出

在C语言中,可以通过至少三种方式来读取键盘输入的值:

  • scanf():和 printf() 类似,scanf() 可以输入多种类型的数据。
  • getchar()getche()getch():这三个函数都用于输入单个字符。
  • gets():获取一行数据,并作为字符串处理。

其中,scanf是格式化扫描的意思,可以通过格式控制符对输入字符进行格式化,并赋值给相关变量。

格式控制符说明
%c读取单一字符
%s读取一个字符串(以空白符为结束)
%f、%lf读取十进制形式小数,赋值给float、double 类型
%e、%le读取指数形式小数,赋值给 float、double 类型
%g、%lg读取十进制或指数形式的小数,
并分别赋值给 float、double 类型
  • 整数格式化
    shortintlong
    十进制%hd%d%ld
    八进制%ho%o%lo
    十六进制%hx%x%lx
    无符号%hu%u%lu

getchar()等价于scanf("%c", c),相对来说更加简单。getchegetch是Windows独有的函数,在头文件conio.h中故不赘述。

getsscanf(%s,s)的区别在于,后者在使用的过程中会把空格当作终止符,而前者不会。

所以,我们在实现连加的过程中,会使用gets作为交互方法。

由于我们实现的是一个连加器,所以输入字符中只包含数字和加号,那么接下来,我们需要遍历输入字符,通过加号来将数字分开。我们可以很方便地写下一个简单而丑陋的小程序。

voidadds(){char str[100];char numStr[20];int num[20];int val;int i,j,k;while(1){gets(str);
        i=0;j=0;k=0;while(str[i]!='\0'){if(str[i]=='+'){
                num[k]=atoi(numStr);
                k++;
                j=0;}else{
                numStr[j]= str[i];
                j++;}
            i++;}
        num[k]=atoi(numStr);
        val=0;for(int i=0; i< k+1; i++){
            val+= num[i];}printf("%d\n",val);}}intmain(){adds();return0;}

由于加减法具有相同的运算优先级,在实现上不过是为后续的数字加上一个负号而已,故可十分方便地在原有程序上修改。

此外,adds代码乍看上去没什么问题,但str的值在更新之前,并不会自动清零,由此带来的bug需要创建一个字符串清零的函数。修改之后的代码如下

#include<stdio.h>#include<stdio.h>#include<string.h>#include<math.h>voidstrClear(char*str,int n){for(int i=0; i< n; i++){
        str[i]=NULL;}}voidadds(){char str[100];char numStr[20];int num[20];int val;int i,j,k;while(1){gets(str);
        i=0;j=0;k=0;while(str[i]!='\0'){if(str[i]=='+'){
                num[k]=atoi(numStr);strClear(numStr,20);
                k++;
                j=0;}elseif(str[i]=='-'){
                num[k]=atoi(numStr);strClear(numStr,20);
                k++;
                numStr[0]= str[i];
                j=1;}else{
                numStr[j]= str[i];
                j++;}
            i++;}
        num[k]=atoi(numStr);strClear(numStr,20);
        val=0;for(int i=0; i< k+1; i++){
            val+= num[i];}printf("%d\n",val);}}intmain(){adds();return0;}

精简一下

#include<stdio.h>#include<stdio.h>#include<string.h>#include<math.h>voidstrClear(char*str,int n){for(int i=0; i< n; i++){
        str[i]='\0';}}voidadds1(){char str[100];char numStr[20];int i,j,val;while(1){gets(str);
        i=0;j=0;val=0;while(str[i]!='\0'){if((str[i]=='+')||(str[i]=='-')){
                val+=atoi(numStr);strClear(numStr,20);
                j=0;if(str[i]=='-')
                    numStr[j++]=str[i];}else
                numStr[j++]= str[i];
            i++;}
        val+=atoi(numStr);strClear(numStr,20);printf("%d\n",val);}}intmain(){adds1();return0;}

2. 加法和乘法

若希望加入乘法和除法,那么修改代码的过程就相对复杂了,因为乘除法在运算过程中,具有比加减法更高的优先级。那么我们就无法通过一个简单的数组来存储变量,而必须建立一种树形的结构。

例如,对于 a + b × c − d × e / f a+b\times c-d\times e/fa+b×cd×e/f,可写成如下形式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ruufs0c0-1608717185925)(img/calc1.png)]
在这里插入图片描述

我们可以看到,这是一个二叉树,每个叶节点都是数字,而两个叶节点的父节点则为运算符。我们通过这个运算符来计算其子节点后,删除它的两个子节点,同时运算符所对应的节点退化为叶节点,同时其类型也变为数字。

对于上图而言,先计算 e / f e/fe/f,然后计算 b × c b\times cb×c d × e / f d\times e/fd×e/f,再计算 b × c − d × e / f b\times c-d\times e/fb×cd×e/f,最后计算最上面的加法。

对于树来说,我们的遍历往往从根节点开始,所以其计算规则如下:

  1. 如果当前节点的子节点为叶节点,则计算当前节点,并删除该节点的叶节点,然后考虑其父节点。
  2. 如果当前节点的某个子节点不是叶节点,则处理该子节点,直到该子节点成为叶节点为止。
  3. 如果当前节点为根节点,且为叶节点,则输出计算结果。

对于节点来说,除了父子节点外,则至少有两个属性:

  1. isLeaf:用于叶节点判定。在这里,叶节点不仅有结构上的意义,更有着明确的语义:它只能是数字。
  2. value:对于叶节点而言,这个值为数字,否则的话,这个值为运算符号及其所对应的计算规则。
#defineMAXLEN100typedefstructNODE{structNODE*father;structNODE*Left;structNODE*Right;char value[MAXLEN];int isLeaf;}Node;

生成计算树

由于我们规定了两个运算层级,所以再遍历字符串以生成计算树的过程中,需要两次循环,即首先生成加减法的计算树,然后再生成乘除法的计算树。

生成计算树的过程可以简化为字符串不断拆分的过程,为了简化思维,我们只考虑两个符号+*,这两个符号分别代表两种计算层级。

由此可得到如下代码。对于

#defineTRUE1#defineFALSE0voidnewNode(Node*root, Node*father){
    root-> father= father;
    root->Left=NULL;
    root-> Right=NULL;
    root-> isLeaf= FALSE;}//root 为根节点,str为字符串voidinitCalcTree(Node*root,char flag){for(int i=0; i< MAXLEN; i++){if(root->value[i]==flag){
            Node*Left=(Node*)malloc(sizeof(Node));
            Node*Right=(Node*)malloc(sizeof(Node));newNode(Left,root);newNode(Right,root);for(int j=0; j< i; j++)
                Left-> value[j]= root->value[j];
            Left->value[i]='\0';
            i++;for(int j= i; j< MAXLEN; j++)
                Right-> value[j-i]= root->value[j];

            root->Left= Left;
            root->Right= Right;strClear(root->value,MAXLEN);
            root->value[0]= flag;
            root->value[1]='\n';initCalcTree(Left,'*');if(flag=='+')initCalcTree(Right,'+');elseinitCalcTree(Right,'*');break;}else{if(root->value[i]=='\0'){if(flag=='+')initCalcTree(root,'*');else
                    root-> isLeaf= TRUE;break;}elsecontinue;}}}

测试一下

voidprintNode(Node*root,int start){printf("the %dth node is %s\n", start, root->value);if(root->isLeaf==FALSE){printNode(root->Left, start+1);printNode(root->Right, start+1);}}intmain(){
    Node*root=(Node*)malloc(sizeof(Node));char*str="1+21*3+3*4*5+6";strcpy(root->value,str);initCalcTree(root,'+');printNode(root,0);return0;}

得到结果为

the0th node is+

the1th node is1
the1th node is+

the2th node is*

the3th node is21
the3th node is3
the2th node is+

the3th node is*

the4th node is3
the4th node is*

the5th node is4
the5th node is5
the3th node is6

然后,我们再对计算树进行计算。当被计算的量为叶节点时,则返回该节点的值;如果该节点的两个节点都是叶节点,则返回该节点处的运算符对这两个子节点的计算值;如果该节点的两个节点都不是叶节点,那么对这两个子节点进行计算。

intcalcNode(Node*root){if(root->isLeaf== TRUE)returnatoi(root->value);elseif(root->Left->isLeaf* root->Right->isLeaf== TRUE){if(root->value[0]=='+')returnatoi(root->Left->value)+atoi(root->Right->value);elseatoi(root->Left->value)*atoi(root->Right->value);}else{if(root->value[0
  • 作者:微小冷
  • 原文链接:https://tinycool.blog.csdn.net/article/details/111595416
    更新时间:2022-10-24 12:57:34