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

简介

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

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

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


原文地址:https://www.cnblogs.com/Stone-Blossom/p/8832745.html