博客二记之四则运算

运算进化

  在博客一记中,随机生成小学30道带有判断功能的加减法,根据需求的变化,在这一记中将有新的需求。不只是简单的加减法,而是要生成四则运算。根据有关资料表明四则运算要有三个及以上的运算符,只有这样的式子运算才能称的上是四则运算。

  • 第一步:继承博客一记中的思想也是将题用一个类表示。相对于博客一记中的类,新的类明显要复杂得多。不只是有两个操作数,一个运算符,必须要有一段空间来保存操作数和运算符。因为要随机生成操作数及运算符,操作数的个数也是不确定的,故采用动态内存分配的方式。
class Problem
{
private:
    int *Operand;//存放操作数
    int *Type;//存放运算符,0+  ,1-  ,2*  ,3/   
    string *Prob;//存放完整的问题

    int opnum;//操作数个数
    int brackets[2];//括号的位置

    int Right_Result;//正确结果
    int Write_Result;//填写的结果
public:
    Problem();
    ~Problem();
    void CreatProblem(int kuohao = 1);//创建题目
    void SaveProblem();//将题目保存到Prob[]
    void ShowProblem();//显示题目
    void Fill_Result();//填写结果
    void Judge_Result();//判断结果

};
  • 第二步,类的创建。将所有的操作数都放在了一个整型数组里,将所有的运算符也都放在数组里,最后形成的问题放到string类数组里。根据实际情况,预先设定宏定
    # define OPERAND_NUM 4 //操作数不小于3,小于3+OPERAND_NUM

Problem::Problem()
{
    opnum = 3+rand()%OPERAND_NUM;//操作数不小于3,小于3+OPERAND_NUM
    Operand = new int[opnum];

    Type = new int[opnum-1];
    Prob = new string[3*opnum];
    for(int t = 0;t<3*opnum;t++)
    {
        Prob[t] = "";
    
    
    }


}


 

第三步,公有成员函数void CreatProblem()的设计。该函数用于问题的生成,有无括号有参数括号的值决定,默认为1,有括号。


void Problem::CreatProblem(int kuohao)
{
    
    for(int i = 0;i<opnum-1;i++)//生成少一个的操作数、生成运算符
    {
        
        Operand[i] = rand()%100;
        Type[i] = rand()%4;
        if(Type[i] == 3&&Operand[i] == 0)//排除除数为0的可能
        {
            
            while(Operand[i] == 0)
            {
                Operand[i] = rand()%100;
            }
        
        }

    }
    Operand[opnum-1] = rand()%100;//生成最后一个操作数
    
    if(kuohao == 1)//有括号
    {
    brackets[0] = rand()%(opnum -1);
    brackets[1] = brackets[0] + 1 + rand()%(opnum -1-brackets[0]);//不在第一个括号的位置且不超过
    }
    else//无括号
    {
    brackets[0] = -1;
    brackets[1] = -1;
    }
 
}


 
  • 第四步,公有成员函数void SaveProblem()的设计。该函数用于问题的保存。
  1. 如何选择保存问题的结构一开始时纠结了好一段时间。

  首先想到的是直接用字符串来保存,可是当要进行计算结果时,用字符串保存明显为后续的递归下降分析来计算结果带来新的问题——区分数字,也就不好办了。也就否决了用字符串来保存问题。

  突然想到c++有string这个标准类,是字符串类,动态申请内存,能够保存多个字符串,保证了Prob里能保证数字不再是单个数而是一串数,也就不用判断组合成数字。 

  2. 如何设计算法将问题保存到结构里。

      前opnum-1个操作数与运算符是成对出现的,而左括号只能出现在操作数前面,右括号只能出现在操作数的后面。在循环进行判断之前声明变量num来记录当前数组的下标。每有成员写入Prob里就将num加1。

void Problem::SaveProblem()
{
    int num = 0;//string数组的下标
    for(int temp =0;temp<opnum-1;temp++)
    {
        char str[10];
        sprintf(str,"%d",Operand[temp]);//将整型转换成字符串
        switch(Type[temp])
        {
            /********case后的循环是去除除数为0的可能*********/
        case 0:
            {
                while((Type[temp-1]==3)&&(Operand[temp]==0))
                {
                    srand(time(0));
                    Operand[temp] = rand()%100; 
                }
                if(temp == brackets[0])
                {        
                    Prob[num] = "(";
                    num++;
                    //strcat(tempprob,"(");          
                }  
                Prob[num] = str;
                num++;
                //strcat(tempprob,str);
                
                if(temp == brackets[1])
                {            
                    Prob[num] = ")";
                    num++;
                    //strcat(tempprob,")");
            
                }
                
                Prob[num] = " + ";
                num++;
                //strcat(tempprob," + ");
                break;
            }
        
        case 1:
            {
                while((Type[temp-1]==3)&&(Operand[temp]==0))
                {
                    srand(time(0));
                    Operand[temp] = rand()%100;
               }
                if(temp == brackets[0])
                {
                    Prob[num] = "(";
                    num++;
                    //strcat(tempprob,"(");      
                }  
                Prob[num] = str;
                num++;
                //strcat(tempprob,str);
                
                if(temp == brackets[1])
                {
                
                    Prob[num] = ")";
                    num++;
                    //strcat(tempprob,")");            
                }
        
                Prob[num] = " - ";
                num++;
                //strcat(tempprob," - ");
                break;
            }
        
        case 2:
            {
                while((Type[temp-1]==3)&&(Operand[temp]==0))
                {
                    srand(time(0));
                    Operand[temp] = rand()%100;             
                }
                if(temp == brackets[0])
                {         
                    Prob[num] = "(";
                    num++;
                    //strcat(tempprob,"(");
             
                }
                Prob[num] = str;
                num++;
                //strcat(tempprob,str);
                
                if(temp == brackets[1])
                {
            
                    Prob[num] = ")";
                    num++;
                    //strcat(tempprob,")");
              
                }

                Prob[num] = " * ";
                num++;
                //strcat(tempprob," + ");
                break;
            }
        
        case 3:
            {
                while((Type[temp-1]==3)&&(Operand[temp]==0))
                {
                    srand(time(0));
                    Operand[temp] = rand()%100;             
                }
                if(temp == brackets[0])
                {   
                    Prob[num] = "(";
                    num++;
                    //strcat(tempprob,"(");
           
                }
      
                Prob[num] = str;
                num++;
                //strcat(tempprob,str);               
                if(temp == brackets[1])
                {   
                    Prob[num] = ")";
                    num++;
                    //strcat(tempprob,")");
              
                }

                Prob[num] = " / ";
                num++;
                //strcat(tempprob," / ");
                break;
            }
        }
    }
        
    /*******最后一个操作数***********/
    char strlast[10];
    sprintf(strlast,"%d",Operand[opnum-1]);

    Prob[num] = strlast;
    num++;
    //strcat(tempprob,strlast);

    if(opnum - 1 == brackets[1])
    {
        Prob[num] = ")";
                
    }
}
  •  第五步,问题显示,结果输入。
  •  第六步,结果的判断。四则运算结果计算,在数据结构中就曾经提到过,可以用二叉树、中序遍历、后续遍历等方法实现。在这次程序中,我采用的是用两个栈的方法。

构建两个栈,一个保存操作数,一个保存算符,从左向右遍历表达式。

  1.遇到数字,压入操作数栈

  2.遇到算符则比较栈顶元素和算符的优先级

  • 1).栈顶元素比算符的优先级低,算符压栈
  • 2).栈顶元素与操作符的优先级相同,证明是配对的()或#,栈顶算符出栈
  • 3).栈顶元素比算符的优先级高,算符出栈1个,操作数出栈2个,计算完的结果压入操作数栈

在优先级确定时,

char Precedure(char a1, char a2)   //a1为前一个运算符,a2为后一个运算符
{
 char r;
 switch(a2)
 {
 case '+':
 case '-':
  if(a1 == '(' || a1 == '#')
   r = '<';
  else
   r = '>';
  break;
 case '*':
 case '/':
  if(a1 == '*' || a1 == '/' || a1 == ')')
   r = '>';
  else
   r = '<';
  break;
 case '(':
  if(a1 == ')')
  {
   cout<<"括号匹配错误"<<endl;
   exit(-1);
  }
  else
   r = '<';
  break;
 case ')':
  if(a1 == '(')
   r = '=';
  else if(a1 == '#')
  {
   cout<<"error,没有左括号"<<endl;
  }
  else
   r = '>';
  break;
 case '#':
  switch(a1)
  {
  case'#':
   r='=';
   break;
  case'(':
   cout<<"error!没有右括号"<<endl;
   exit(-1);
  default:
   r='>';
  }//switch
  break; 
 }
 return r;
}
  • 最后就是主函数的设计来实现定制数目、是否包含括号等功能,这一部分相对于前面几部分要容易的多,有一点需要注意,就是Problem类声明之前要有srand(time(0)),而非在类的声明里,如果是在里面的话,生成的题目的操作数的个数都是一样的会出现如下情况,失去了随机的意义。

Problem类声明之前有srand(time(0)),类里没有,才真正实现操作数目的随机性,如下是修改后的情况。

经网上资料及亲身实践知srand()反应时间大概50~60ms,而循环是很快就完成了,因此要将srand()放在循环外,才有用。

  好了,这个四则运算题就此结束,具体代码见  https://coding.net/u/zht01/p/Four_arithmetic-_operations/git/blob/master/kidtest_2.cpp

原文地址:https://www.cnblogs.com/zht01/p/5270100.html