结对编程_四则表达式生成

简介

本次开发的软件是为帮助小学老师解决出题麻烦且不高效的问题,经过和伙伴尉安瑞的共同合作,制作出了一个能够自动生成四则运算且能计算出结果的软件。

  • 操作系统:Window 10
  • 语言:C++,Java
  • 开发环境:CodeBlocks,Eclipse
  • 开发人员:Stone,尉安瑞
  • Github

一. 软件功能介绍

  • 能够自动生成四则运算练习题
  • 可以定制题目数量
  • 用户可以选择运算符
  • 用户设置最大数(如十以内,白以内等)
  • 用户选者是否有括号、是否有小数
  • 用户选择输出方式(如输出到文件、打印机等)
  • 提供图形界面

1345550-20180416183658525-1740391370.png
1345550-20180416183711437-1561876309.png
1345550-20180416183719328-1741799621.png
1345550-20180416183727110-332085243.png
1345550-20180416183735882-496355090.jpg

二. 设计方法

在一开始设计的时候,伙伴明确的指出采用MVC模式进行软件设计,赶紧百度一下什么是MVC,原来这是一种软件设计典范,把软件分成三部分模型(Model)、视图(View)和控制器(Controller)进行开发,这样会更高效,视图层和业务层分离,代码耦合性低,复用性高,不至于出现改一处而改全部,让软件无法开发下去。
1345550-20180416183628367-1302961091.png

软件分成三个部分进行开发

  • Model(模型)模块主要存放的是程序的原语操作部分
  • View(视图)模块是展示给用户并让用户进行输入操作的部分
  • Controller(控制器)模块是处理输入,并对底层操作进行控制的部分

1. 我们按照这个模式进行讨论,首先是对最底层M的设计

  • 随机数的生成
  • 随机操作数生成
  • 随机操作符生成
  • 随机括号的生成
  • 是否有小数
  • 四则运算

这些都是原子操作,只受C层控制,其中值得一提的是在设计随机操作符生成时,伙伴想到使用8,4,2,1来表示+,-,*,/操作符的代号之和,之后只要知道代号和,就能得到总共有几个操作符参与运算,它们都是什么,并且在进行存储的时候就可以和操作数一起进行存储,大大降低了存储的复杂度。

2. 其次是对C层的设计

  • 表达式及结果的生成和存储
  • 查重操作

C层依赖于M层,只要进行对M层的调用,再进行存储即可

3. 最后是V 层的设计

  • 图形话界面
  • 文件的生成

由于是结对编程,M,C层的驾驶员主要由我负责,V层则由伙伴负责,这层主要设计请参考这里

三. 代码(部分代码)

1. Model模块

1.1 随机数生成
/**
*Summary: 随机数生成
*Parameters:
* range: 随机数范围
**/
int random(int range)
{
    //srand(time(0));
    return rand()%range;
}
1.2 随机操作符生成
/**
*Summary:随机操作符生成
*Parameters:
* opreation: 表示操作符代号之和
*   + :加法,用数字8表示
*   - :减法,用数字4表示
*   * :乘法,用数字2表示
*   / :除法,用数字1表示
*return: 操作符代号
**/
int getOperation(int opreation)
{
    int count = 0;
    int i = 0;
    int temp = opreation;
    for(;i<4;i++)
    {
        count+=(temp&1);
        temp = temp>>1;
    }
    count =  random(count);
    for(i = 1;i<=8;i=i*2)
    {
        if((opreation&i) != 0)
        {
            if(count==0)
            {
                return i;
            }
            else
            {
                count--;
            }
        }
    }
}
1.3 随机操作数数生成
/**
*Summary:随机操作数数生成
*Parameters:
* max: 操作数取值上限
* decimal: true 表示小数 false 表示整数
* negative: true 表示负数 false 表示正数
*return: 随机操作数
**/
float getOperand(int max, bool decimal, bool negative)
{
    if(!decimal && !negative)
    {
        return random(max);
    }
    else if(!decimal && negative)
    {
        return -1*random(max);
    }
    else if(decimal && !negative)
    {
        return random(max)/100.0+random(99);
    }
    else
    {
        return -1*(random(max)/100.0+random(99));
    }
}
1.4 随机括号生成
/**
*Summary:随机括号生成
*Parameters:
* max: 表示左括号 max+1表示右括号
* left_bracket: 未匹配的左括号个数
*return: 括号代号
**/
int getBracket(int max, int &left_bracket)
{
    if(left_bracket == 0)
    {
        return max;
    }
    else
    {
        return max+random(2);
    }
}
1.5 运算
/**
*Summary: 进行运算
*Parameters:
* myExpression: 算术表达式
**/
float evaluateExpression(char* myExpression)
{
    // 算术表达式求值的算符优先算法
    // 设OPTR和OPND分别为运算符栈和运算数栈,OP为运算符集合
    SC *OPTR=NULL;                                   // 运算符栈,字符元素
    SF *OPND=NULL;                                   // 运算数栈,实数元素
    char tempData[20];                               // 用来存储、转换操作数
    float data, a, b;
    char theta, *c, Dr[] = {'#', '\0'};              // Dr[]的'#'用来使传进来的字符串尾为'#','\0'是字符串结束的标志,控制其长度
    OPTR = push(OPTR, '#');
    c = strcat(myExpression, Dr);
    strcpy(tempData, "\0");                         //字符串拷贝函数,让tempData[0]为"\0"
    while (*c != '#' || OPTR->c != '#')
    {
        if (!in(*c, OPSET))
        {
            Dr[0] = *c;
            strcat(tempData, Dr);               //字符串连接函数
            c++;
            if(in(*c, OPSET))
            {
                data = atof(tempData);          //字符串转换函数(double)
                OPND = push(OPND, data);
                strcpy(tempData,"\0");
            }
        }
        else if(in(*c, OPSET))                  // 不是操作数则进栈
        {
            switch(precede(OPTR->c, *c))
            {
            case '<': // 栈顶元素优先级低
                OPTR = push(OPTR, *c);
                c++;
                break;
            case '=': // 脱括号并接收下一字符
                OPTR = pop(OPTR);
                c++;
                break;
            case '>': // 退栈并将运算结果入栈
                if(OPTR->c == '#')
                {
                    cout << "输入错误!";
                    break;
                }
                theta= OPTR->c;
                OPTR = pop(OPTR);

                if(OPND==NULL)
                {
                    flag = false;
                    return 0;
                }
                b = OPND->f;
                OPND = pop(OPND);

                if(OPND==NULL) 
                {
                    flag = false; 
                    return 0;
                }
                a = OPND->f;
                OPND = pop(OPND);

                float p = operate(a, theta, b);             //p用来记录运算后的结果
                if(!flag) return 0;                         //若运算不合法跳出函数
                OPND = push(OPND, p);
                break;
            } //switch
        }
    } //while
    return OPND->f;
} //evaluateExpression

2. Controller模块

2.1 查重
/**
*Summary: 查重
*Parameters:
* rep: 生成的表达式仓库
* flag: 新生成的表达式下标
*return: false表示没有重复的表示式 true反之
**/
bool recheck(Repertory* rep,int flag)
{
  for(int i = 0; i<flag; i++)//遍历flag行之前的表达式
  {
      bool target = true;
      for(int j = 0; j<(rep->col-1); j++)
      {
          if((rep->array[i][j]!=rep->array[flag][j]))
          {
              target = false;
          }
      }
      if(target)
      {
          return true;
      }
  }
  return false;
}
2.2 输出到控制台
/**
*Summary: 输出到控制台
*Parameters:
* rep: 生成的表达式仓库
* output: 0表示输出到控制台,1表示输出到文件,2表示输出到打印机
* isResult: true表示计算出结果,false表示不计算出结果
* isBracket:ture表示有括号,false表示没有括号
**/
void getResult(Repertory* rep, bool isResult, bool isBracket)
{
    int n = 0; //记录左括号数
    bool turn = false; //标志,当操作符后有括号时为true,避免出现一个操作数被一对括号套住
    for(int i = 0;i<(rep->row);i++)
    {
        char equation[100]={'0'};
        string str="";
        for(int j = 0;j<(rep->col-1);j++)
        {
            if(j%2==0)
            {
                if(j==0&&isBracket&&random(2))//第一位操作数是否生成左括号
                {
                    str+="(";
                    cout << "(";
                    n++;
                }
                str+= to_string(rep->array[i][j]); //将操作数放到字符串str中

                if(j!=0&&isBracket&&random(2)&&n!=0&&!turn)//是否需要生成右括号
                {
                    n--;
                    str+=")";
                    cout<<rep->array[i][j]<<")";
                }
                else
                {
                    turn = false;
                    cout<<rep->array[i][j];            //打印单个操作数
                }
            }
            else
            {
                str+=configure[(int)rep->array[i][j]-1];//将操作符放到字符串str中
                if(isBracket&&random(2)&&((rep->col-j)/2>n+1))    //是否需要生成左括号
                {
                    turn = true;
                    n++;
                    str+="(";
                    cout<<configure[(int)(rep->array[i][j])-1]<<"(";
                }
                else
                {
                    cout<<configure[(int)(rep->array[i][j])-1];//打印单个操作符
                }
            }
        }
        while(n!=0)
        {
            str+=")";
            cout << ")";
            n--;
        }
        cout << "=";
        strcpy(equation, str.c_str()); //将字符串str转换为字符数组equation
        if(isResult)
        {
            calculate(equation);  //计算并打印出结果
        }
        cout <<endl;
    }
}

四.对伙伴的评价

小伙伴是一位特别有经验的人,在拿到题目时,他首先提出了这次软件的开发模式,并很快的进行模块化的分解,这也是本次开发能够快速高效进行的一大重要因素;在开发过程中,他思维敏捷、清晰,总是能在关键的地方提出非常有建设性的建议,就像当我们在确定如何生成随机运算符时,他给出了一个很高效的算法,把+,-,*,/用数字8,4,2,1表示,并用他们之间的代数和来表示总共有几种操作符及都是哪些,这样一来即解决了问题,也避免了去使用多重条件语句来判断,提高了编程的效率;同时他也是一位稳重的人,在我们进行讨论的时候,我总是一有想法就立马讲出来,并没有经过自己的仔细推敲,往往会造成打乱我们的编程节奏,使之偏离方向,而伙伴则会经过仔细的推敲验证后才会把自己的想法提出来,并且可靠性很强;他还是一位有耐心,有团队精神的人,在出现我没搞明白的地方时,他会耐心的和我讲解,让我很快的跟上节奏;他很注重细节,在给变量、函数等命名时,一定要找到一个能很容易看明白的词,并且一开始就和我统一编码风格和注释风格,应为这是结对编程,不是一个人进行开发,让对方很快的明白代码的意思很重要。

总之,他的这些优点都是值得我去学习的,很开心能和他一起合作开发。

五.总结

本次结对编程, 对我感触最大的就是建一个适合该工程的好模型。可能对于一个小项目,拿来直接就上手,一般来说都没太大问题,但只要稍微的加大点难度,再增加点功能,没有一个好的模型支撑,写到后面肯定是要乱的,即使你写下来了,可扩展性和可维护性是极差的。在这次编程中,我主要负责C,M层的底层开发,刚开始,都把每一个功能尽可能的模块化,一切开发的都挺顺利,但到了添加随机括号时,因为当时就只差这个功能了,就只想着怎么快速的实现,结果最后,虽然实现了,但导致代码的复用性极低,甚至只要稍微该一点,就得对代码进行重构,这一点非常不利于对项目的管理和维护。同样,结对编程最大的特点就是需要两人沟通,自己写代码可能不是问题,可怎么把自己的idea有效的传递给对方,这就需要自己多加练习。

总的来说,这次结对编程让我学习到了很多,不仅在编程、设计方面,友谊方面也得到了收获,期待着下一次和他的合作。

1345550-20180416183808014-325890991.jpg
1345550-20180416183827465-1767223212.jpg

猜你喜欢

转载自blog.csdn.net/qq_36784975/article/details/84532882
今日推荐