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


    简介

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

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

    一. 软件功能介绍

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





    二. 设计方法

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

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

    • 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[] = {'#', ''};				 // Dr[]的'#'用来使传进来的字符串尾为'#',''是字符串结束的标志,控制其长度
    	OPTR = push(OPTR, '#');
    	c = strcat(myExpression, Dr);
    	strcpy(tempData, "");							//字符串拷贝函数,让tempData[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,"");
    			}
    		}
    		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有效的传递给对方,这就需要自己多加练习。

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


  • 相关阅读:
    4.计算机启动过程的简单介绍 计算机启动流程 计算机BIOS作用 POST 开机自检 计算机启动顺序 分区表 操作系统启动
    3.操作系统简单介绍 操作系统发展历史 批处理分时系统 操作系统是什么 操作系统对文件的抽象 进程 虚拟内存是什么 操作系统作用 操作系统功能
    2.计算机组成-数字逻辑电路 门电路与半加器 异或运算半加器 全加器组成 全加器结构 反馈电路 振荡器 存储 D T 触发器 循环移位 计数器 寄存器 传输门电路 译码器 晶体管 sram rom 微处理 计算机
    1.计算机发展阶段 计算机发展历史 机械式计算机 机电式计算机 电子计算机 逻辑电路与计算机 二极管 电子管 晶体管 硅 门电路 计算机 电磁学计算机二进制
    如何解决svn清理失败 不能更新 cleanup失败 cleanup乱码 更新乱码 svn更新提示清理 清理乱码不能清理 svn故障修复SVN cleanup 陷入死循环 svn cleanup时遇到错误怎么办
    eclipse svn插件卸载 重新安装 Subclipse卸载安装 The project was not built since its build path is incomplete This client is too old to work with the working copy at
    java for循环里面执行sql语句操作,有效结果只有一次,只执行了一次sql mybatis 循环执行update生效一次 实际只执行一次
    windows资源管理器多标签打开 windows文件夹多标签浏览 浏览器tab页面一样浏览文件夹 clover win8 win10 报错 无响应问题怎么解决 clover卡死 clover怎么换皮肤
    批处理启动vm虚拟机服务 vm12启动无界面启动vm虚拟机系统 windows上如何操作服务 sc net启动关闭服务
    不能ssh连接ubuntu linux 服务器 secureCRT不能ssh连接服务器 不能远程ssh连接虚拟机的ubuntu linux
  • 原文地址:https://www.cnblogs.com/Stone-Blossom/p/8832745.html
Copyright © 2020-2023  润新知