zoukankan      html  css  js  c++  java
  • 结对编程_四则表达式生成

    简介

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

    • 操作系统: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有效的传递给对方,这就需要自己多加练习。

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


  • 相关阅读:
    判断是否可以点击
    窗口截图
    设置等待操作
    时间控件处理
    eclipse小技巧
    Angular 学习1
    MVC 中引用Angularjs
    Bootstrap 侧边栏 导航栏
    C# 直接使用sql语句对数据库操作 (cmd.ExecuteNonQuery)
    sql 常用的语句(sql 创建表结构 修改列 清空表)
  • 原文地址:https://www.cnblogs.com/Stone-Blossom/p/8832745.html
Copyright © 2011-2022 走看看