kkzz 发表于 2025-6-23 13:59

四则运算计算器算法

/**************************************************************************************
                *                              四则运算计算器                                                                                                  *
                *          键盘使用:s1-s16分别为:0,1,2,3,4,5,6,7,8,9,+,-,*,/,(,),共16个键
                *                k1-k3分别为:= ~ .
                *   连接方法:P24连K1, J20连JP3, 插上LCD1602
                *   三步走策略,如下:
                *          1.输入,解决按键响应问题。2022.1.1解决!
                *   2.输出,解决显示问题。        2022.1.2解决!2022.1.3完善!
                *   3.运算,解决四则运算问题。
                          (1)解决状态机问题。   2022.1.4解决!
                                (2)确定状态机能工作                2022.1.5解决!
                                (3)解决运算问题。                2022.1.6初成!
                *   4.排查,检测边界情况。        2022.1.6大致解决,仍有瑕疵!                                                                      
                *                                                                                                                                                                          *
                ***************************************************************************************/
               
                #include <reg51.h>           //此文件中定义了51的一些特殊功能寄存器
                #include "stdio.h"
                #include "stdlib.h"           //此文件中有内存池初始化函数
                #include <math.h>
                #define uchar unsigned char
                #define uint unsigned int
                #defineMAX_LEN 10
                sbit EN=P2^7;//LCD的使能引脚
                sbit RS=P2^6;//LCD数据命令选择端
                sbit RW=P2^5;//LCD的读写选择端
               
                //19个键盘
                sbit K1=P2^4;   //= 键
                sbit K2=P2^3;   //~ 键
                sbit K3=P2^2;   //. 键
                uchar KEY_CODE[]={ 0xee,0xde,0xbe,0x7e,//4X4矩阵键盘键值表,0-9, + - * / ( ), 共16个
                                                   0xed,0xdd,0xbd,0x7d,
                           0xeb,0xdb,0xbb,0x7b,
                           0xe7,0xd7,0xb7,0x77};
                //定义字符键值表
                uchar CHAR_TABLE[]={0x30,0x31,0x32,0x33,//这四个会在液晶显示器中显示0 1 2 3
                                    0x34,0x35,0x36,0x37,//这四个会显示4 5 6 7
                                                        0x38,0x39,0x2b,0x2d,//这个四个会显示8 9 + -
                                                        0x2a,0x2f,0x28,0x29,//这个四个会显示* / ( )
                                                        0x3d,0x7e,0x2e};        //这三个会显示= ~ . 共19个
                //运算优先级表,不使用,太占内存
                /*uchar PRIORITY[]={'>','>','<','<','<','>',//先行后列,顺序为+ - * / ( )
                                                  '>','>','<','<','<','>',
                                                  '>','>','>','>','<','>',
                                                  '>','>','>','>','<','>',
                                                  '<','<','<','<','<','=',
                                                  '>','>','>','>',' ','>'};*/
       
                void scanf_(uchar *var);//从矩阵键盘中获取值
                void print(uchar outStr);//打印字符串
                void delay5MS();
                void delay100MS();

                void writeCMD(uchar com);//写命令子程序
                void showOneChar(uchar dat);//写数据子程序
                void init();//初始化子程序,初始化液晶显示屏
                void clear();//清除显示屏上的显示

                //核心处理函数
                uchar stateChange(uchar inp);
                //比较优先级函数
                uchar cp_priority(uchar cur);
                //计算函数
                void calculate(uchar inp);
                //检查除法异常函数
                uchar jd_abnormal();
                //状态刷新函数,核心处理函数开头
                void rf_state();
                //获取状态类别
                uchar getClass(uchar var);
                //压栈函数
                void push_st(uchar inp);
                //更新tmp值
                void getTmp(uchar inp);
                //最终运算
                void last_cal(double op2, double op1, uchar opr);
                //显示最终结果
                void showAns();


                //建立堆栈               
               uchar str;//符号堆栈
               double std;//数堆栈
               //栈顶位置
               int topd = -1;
               int topr = -1;
               //暂存数据
               double tmp = 0;
               //状态标志位
               uchar state = 0;//共8个状态,具体所指请看文档
               uchar current = 0;//指示当前状态,tmp处理使用
               uchar bracket = 0;//括号状态,0无或有右括号,1有左括号
               uchar negative = 0;//负号状态,0无符号,1有负号
               uchar point = 0;//点状态,0无点,1有点
               uchar pl = 0;//点位置,0初始位,值为当前是点后第几位
               uchar sfs = 0;//可能的除0异常状态,0表示当前可能异常,1代表没异常
               
               
        void main()
                {

               //准备工作,其中num为获取的键盘字符。
               uchar num=0xff;
               //app
               int ts = 0;
               init();
               //初始化堆栈
               //init_mempool(&str, sizeof(str));
               //init_mempool(&std, sizeof(std));

               while(1)
               {                     
                       scanf_(&num);
                        current = getClass(num);//(1)获取当前输入的状态
                        if(stateChange(num))//当输入有效
                        {
                                //app处理满屏问题
                                if(ts == 10)
                                {
                                        init();
                                        ts = 0;
                                }
                                push_st(num);
                                if(num != '=')//等号特殊,必须先输出
                                        print(num);                       
                                state = current;//(1)更新state状态
                               
                                //app
                                ++ts;       
                        }       
               }
                }
   /**********从键盘获取值得函数类似于C语言的scanf()函数**************/                               
        void scanf_(uchar *var)
                {
                   uchar temp,num;
                   int i=1;
                   temp=i;

                  
                  
                        while(1){

             if(!K1)
                       {
                               delay100MS(); //延时,软件消除抖动。
                             if(!K1)
                               {
                                       while(!K1);
                                  *var = '=';
                                        break;
                               }
                       }
                       else if(!K2)
                       {
                               delay100MS(); //延时,软件消除抖动。
                             if(!K2)
                               {
                                       while(!K2);
                                  *var = '~';
                                        break;
                               }
                       }
                       else if(!K3)
                       {
                               delay100MS(); //延时,软件消除抖动。
                             if(!K3)
                               {
                                       while(!K3);
                                  *var = '.';
                                        break;
                               }
                       }

                     P1 = 0x0f;//置行为高电平,列为低电平。这样用于检测行值。
                       if(P1!=0x0f)
                       {
                             delay100MS(); //延时,软件消除抖动。
                               temp=P1; //保存行值
                               P1=0xf0; //置行为低电平,列为高电平,获取列
                                   if(P1!=0xf0)
                                {
                                num=temp|P1; //获取了按键位置
                                //P2=1;
                                for(i=0;i<16;i++)
                                   if(num==KEY_CODE)
                                        {
                                          
                                           if(i==10)*var='+';//获取加号的值
                                     else if(i==11)*var='-';//获取减号的值
                                           else if(i==12)*var='*';//获取乘号的值
                                           else if(i==13)*var='/';//获取除号的值
                                           else if(i==14)*var='(';//获取左括号号的值
                                           else if(i==15)*var=')';//获取右括号的值
                                   else *var=i;//获取数值
                                   
                                  }
                                   break;        //跳出循环,为了只获取一个值
                               }
                       }       
                        }

                  }

        /******************显示函数***************************/
    void print(uchar arr)
        {       
    uint t=0,j=0;
        uint location;
       if(arr == '=') location = 16;
       else if(arr == '~')location = 17;
       else if(arr == '.')location = 18;
       else if(arr == '+')location = 10;
       else if(arr == '-')location = 11;
       else if(arr == '*')location = 12;
       else if(arr == '/')location = 13;
       else if(arr == '(')location = 14;
       else if(arr == ')')location = 15;
       else{
                for(j=0;j<10;j++)
                if(arr == j)location = j;
               }

       showOneChar(CHAR_TABLE);       
          
        }
               
    /*********************短延时函数*************************/
                  void delay5MS()
                  {
                  int n=3000;
                        while(n--);
                  }

   /*****************定义长点的延时程序**********************/
       void delay100MS()
          {
          uint n=10000;
                while(n--);
          }
   /*******************写命令子程序**************************/
           void writeCMD(uchar com)
        {
          P0=com;          //com为输入的命令码。通过P2送给LCD
          RS=0;      //RS=0选择指令寄存器
            RW=0;           //RW=0为写
                delay5MS();          
                EN=1;      //LCD的使能端E置高电平,读取信息
                delay5MS();
                EN=0;       //LCD的使能端E置低电平,执行指令
          
        }
/*******************写数据子程序**************************/
        void showOneChar(uchar dat)
        {
                P0=dat;           //写入数据
                RS=1;      //RS=1选择数据寄存器
                RW=0;           //RW=0为写
                EN=1;           //读取信息
                delay5MS();
                EN=0;           //执行指令
          
       }
/*******************初始化函数**************************/
       void init()
       {
                EN=0;      
                writeCMD(0x38);//设置显示模式
           writeCMD(0x0e);//光标打开,不闪烁
                writeCMD(0x06);//写入一个字符后指针地址+1,写一个字符时整屏不移动
                writeCMD(0x01);//清屏显示,数据指针清0,所以显示清0
                writeCMD(0x80);//设置字符显示的首地址,指明了数据块的首地址为P0
       }

/*********************清屏子程序**********************/
void clear()
{
    EN=0;
        writeCMD(0x01);
   
}


/*********************核心处理函数**********************/
uchar stateChange(uchar inp)
{
//刷新所有状态
rf_state();
//状态机分类处理
if(inp>=0&&inp<=9)//1类输入*********
{
   
        if(4 != state && 7 != state)//有效,但要考虑可能的异常情况
        {
                //考虑除数异常状况
                if(inp == 0&&state==2&&str=='/')
                        sfs = 1;
                else
                        sfs = 0;
                //考虑点可能存在
                if(1 == point)
                        pl++;
                //处理tmp入栈,注意负号,点存在,点位置-------------交给push函数了-------------------               
        return 1;
        }
}
else if(inp=='+'||inp=='-'||inp=='*'||inp=='/')//2类输入*********
{
        if((state==1||state==4||state==7)&&!jd_abnormal())//有效,
        {
                if(topr != -1)//当符号栈不空,才进入比较与计算
                calculate(inp);
        return 1;
        }
}
else if(inp=='(')//3类输入*********
{
        if((state==0||state==2)&&bracket==0)//有效
        {
        bracket = 1;
        return 1;
        }
}
else if(inp==')')//4类输入*********
{
        if(state==1&&bracket==1&&!jd_abnormal())//有效
        {
        //test,(数)没考虑
        calculate(inp);
        bracket = 0;
        return 1;
        }
}
else if(inp=='~')//5类输入*********
{
        if(state==0||state==2||state==3)//有效
        {
        negative = 1;
        return 1;
        }
}
else if(inp=='.')//6类输入*********
{
        if(state==1&&point==0)//有效
        {
        point = 1;
        return 1;
        }

}
else if(inp=='=')//7类输入*********
{
        if(bracket==0&&(state==1||state==4)&&!jd_abnormal())//有效
        {
        print(inp);//等号特殊,先显示等号
        calculate(inp);//再进行计算
        return 1;
        }
}

return 0;//0为无效,1为有效
}

/********************比较优先级函数**********************/
uchar cp_priority(uchar cur)//竖着输入,cur为当前有效输入
{
uchar res='=';
if(cur=='+')//输入是加号
{
        if(str=='+')
                res = '>';
        else if(str=='-')
                res = '>';
        else if(str=='*')
                res = '>';
        else if(str=='/')
                res = '>';
        else if(str=='(')
                res = '<';
        else if(str==')')
                res = '>';
}
else if(cur=='-')//输入是减号
{
        if(str=='+')
                res = '>';
        else if(str=='-')
                res = '>';
        else if(str=='*')
                res = '>';
        else if(str=='/')
                res = '>';
        else if(str=='(')
                res = '<';
        else if(str==')')
                res = '>';
}
else if(cur=='*')//输入是乘号
{
        if(str=='+')
                res = '<';
        else if(str=='-')
                res = '<';
        else if(str=='*')
                res = '>';
        else if(str=='/')
                res = '>';
        else if(str=='(')
                res = '<';
        else if(str==')')
                res = '>';
}
else if(cur=='/')//输入是除号
{
        if(str=='+')
                res = '<';
        else if(str=='-')
                res = '<';
        else if(str=='*')
                res = '>';
        else if(str=='/')
                res = '>';
        else if(str=='(')
                res = '<';
        else if(str==')')
                res = '>';
}
else if(cur=='(')//输入是左括号
{
        if(str=='+')
                res = '<';
        else if(str=='-')
                res = '<';
        else if(str=='*')
                res = '<';
        else if(str=='/')
                res = '<';
        else if(str=='(')
                res = '<';
        else if(str==')')
                res = ' ';
}
else if(cur==')')//输入是右括号
{
        if(str=='+')
                res = '>';
        else if(str=='-')
                res = '>';
        else if(str=='*')
                res = '>';
        else if(str=='/')
                res = '>';
        else if(str=='(')
                res = '=';
        else if(str==')')
                res = '>';
}
else if(cur=='=')//遗漏情况
        res = '>';

return res;//res为比较符号
}
/*********************计算函数**********************/               
void calculate(uchar inp)
{
//操作数和操作号
double op1 = 0;
double op2 = 0;
uchar opr = '=';

uchar cp = '=';
        cp = cp_priority(inp);
        if(cp == '>')//执行运算-------注意=情况没讨论------------------------------
        {
                if(inp == ')')//右括号情况特殊,
                {
                        //对于括号内部,可能需要连环操作,如(1+2*3)=
                        while(str!='(')
                        {
                                opr = str;//运算号出栈

                                op1 = std;//运算数1出栈
                                op2 = std;//运算数2出栈

                                last_cal(op2, op1, opr);
                        }
                        //对于括号外部,左括号的到来容易造成堆积---如1+2*(7),留给最后等号处理--------------------
                        topr--;
                        //test
                        //print(str);
                }               
                else
                {
                        opr = str;//运算号出栈

                        op1 = std;//运算数1出栈
                        op2 = std;//运算数2出栈

                        last_cal(op2, op1, opr);
                }
                               
                //test,error,can't go here------------因为在if中------------------------                       
        }

        if(cp == '=')//左括号和右括号临近时
                topr--;

        if(inp == '=')//最终结果可能需要多次运算,直到栈内剩下一个数
        {
                //test
                /*print('*');
                tmp=op2;
                showAns();
                print(opr);
                tmp=op1;
                showAns();
                print('*');*/

                while(topd!=0)//没到底,就算到底----------------------------------
                {
                        opr = str;//运算号出栈

                        op1 = std;//运算数1出栈
                        op2 = std;//运算数2出栈

                        last_cal(op2, op1, opr);
                }
                tmp = std; //到底了,输出结果
                showAns();
               
        }

}
/*********************检查除法异常函数**********************/
uchar jd_abnormal()
{
        if(sfs==1)
        {       
        //数出栈--------------------------------------------------------
        topd--;
        //回复到前前状态
        sfs = 0;
        state = 2;
        //输入无效
        return 1;
        }
return 0;//0为正常,1为异常   
}

/*********************状态刷新函数**********************/
void rf_state()
{
        if(state!=1&&state!=5&&state!=6)//当上一个输入和数无关,重置和数相关的所有状态位和暂存数
        {
        negative = 0;
        point = 0;
        pl = 0;
        tmp = 0;//不知为何出错-----------旧版本编译器缺少处理浮点数的c51fs.lib文件----------------------------------------------------
        }
}

/*********************获取状态类别函数*********************/
uchar getClass(uchar inp)
{
if(inp>=0&&inp<=9)
        return 1;

else if(inp=='+'||inp=='-'||inp=='*'||inp=='/')
        return 2;

else if(inp=='(')
        return 3;

else if(inp==')')
        return 4;

else if(inp=='~')
        return 5;

else if(inp=='.')
        return 6;

else if(inp=='=')
        return 7;

return 0;
}
/*********************压栈函数*********************/
void push_st(uchar inp)
{
        if(current==1)//压数,注意考虑和数相关的negative,point,pl,tmp----------------------------------
        {
                getTmp(inp);//有效输入到当前为止的数是tmp
                if(state!=1&&state!=6)//当上一次输入的不是数字,说明上一次没存数,入栈
                {
                        topd++;
                        std = tmp;
                }
                else //当上一次输入的是数字,则覆盖上次的数
                        std = tmp;
        }
        else if(current==2||current==3||current==4)//压符号[=-*/][(][)]
        {
                if(inp != ')')//右括号需要特殊处理
                {
                        topr++;
                        str = inp;
                }

                /*if(str ==')'&&str=='(')//当栈顶是右括号,并且临近为左括号,两者一起出栈
                {
                        topr--;
                        topr--;
                }*/
        }
               
}
/*********************压栈函数*********************/
void getTmp(uchar inp)//考虑负号,点,点位置
{
          uchar i = 1;
          double base = 1;
          char tpch;

          //tmp = abs(tmp);//可能有副作用------舍去---------------------------------
          sprintf(tpch, "%g", tmp);//判断栈顶数正负的准备
          if(point==0)//当不存在小数
          {
                        if(tpch>='0'&&tpch<='9')
                                  tmp = tmp*10+inp;
                        else
                                tmp = tmp*10-inp;
                               
          }
          else//当存在小数
          {
                  //获取小数部分
                  for(i=1; i<=pl; ++i)
                {
                        base = base/10*1.0;
                }
                        if(tpch>='0'&&tpch<='9')
                                tmp = tmp+base*inp;
                        else
                                tmp = tmp-base*inp;
                //test-----fixed--------
          }

          if(negative == 1)//存在负号
          {
                  if(tpch>='0'&&tpch<='9')
                        tmp = (-1.0)*tmp;//abs副作用可能----确实,小数点后消失-----------------------
          }
                 

                //test
                //if(topd == 0+0x30)
                test

                //test('*');
               
                 
}
/*********************显示答案函数*********************/
void showAns()
{
    char bun;
        uint m = 0;
        tmp *= 1.0;//如果不乘以1.0,会出现小数点后面消失的情况

        sprintf(bun,"%g",tmp);
        while(1)
        {
              if(bun == '\0')
          break;
              else
                  {
                        if(bun == '.'||bun == '-')
                                print(bun);       
                        else       
                                  print(bun-0x30);
                               m++;
              }
       }
}
/*********************显示答案函数*********************/
void last_cal(double op2, double op1, uchar opr)
{
    //计算
        if(opr == '+')
                tmp = op2 + op1;
        else if(opr == '-')
                tmp = op2 - op1;
        else if(opr == '*')
                tmp = op2 * op1;
        else if(opr == '/')
                tmp = op2 / op1 * 1.0;
        //结果入栈
        topd++;
        std = tmp;
}



9dome猫 发表于 2025-6-30 16:24

代码已实现了按键输入、LCD 显示和基本状态机框架

远芳侵古道 发表于 2025-6-30 23:35

边界情况处理
除零错误:在除法运算时检查除数是否为零
括号匹配:确保左右括号数量一致
运算溢出:检测运算结果是否超出范围
输入长度限制:限制表达式最大长度

狗啃模拟 发表于 2025-8-31 23:48

基于逆波兰表达式的算法结构清晰
页: [1]
查看完整版本: 四则运算计算器算法