结对编程_四则表达式生成
简介
本次开发的软件是为帮助小学老师解决出题麻烦且不高效的问题,经过和伙伴尉安瑞的共同合作,制作出了一个能够自动生成四则运算且能计算出结果的软件。
一. 软件功能介绍
- 能够自动生成四则运算练习题
- 可以定制题目数量
- 用户可以选择运算符
- 用户设置最大数(如十以内,白以内等)
- 用户选者是否有括号、是否有小数
- 用户选择输出方式(如输出到文件、打印机等)
- 提供图形界面
二. 设计方法
在一开始设计的时候,伙伴明确的指出采用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[] = {'#', '\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有效的传递给对方,这就需要自己多加练习。
总的来说,这次结对编程让我学习到了很多,不仅在编程、设计方面,友谊方面也得到了收获,期待着下一次和他的合作。