结对作业——随机生成四则运算(Core 第7组)
结对作业
——随机生成四则运算(core第7组)
吕佳玲 PB16060145
涂涵越 PB16060282
GITHUB地址
https://github.com/hytu99/homework_2
(7_Arithmetic 文件夹内有最新的.dll、.lib、.h文件以及API文档)
项目简介
这次软件工程结对项目为制作一个给小学生用的四则运算出题软件,然后我们抽到的是Core组,也就是负责计算核心——随机生成四则运算——这一部分,并将其封装成dll模块,供UI组使用。
项目分析
首先,我们对外,就是接受UI传进来的相关参数,然后生成题目,并将生成的题目及相应的结果以字符串数组的形式返回给UI。
而我们自己内部,则是按照要求(表达式类型、运算符数目、运算符种类等)生成随机一定数量题目。就随机生成题目的方式,为了更方便地满足所要求的限定条件,通过讨论,我们采用的是通过循环,按照运算的顺序逐步生成完整的表达式,在生成的过程中通过比较运算符的优先级以及逐步计算中间结果的方式,来保证不出现不必要的括号以及中间结果不出现负数。
代码架构与具体实现
(1)对外为一个arithmetic类
class arithmetic {
private:
typedef struct expNode {
string exp;
string ans;
}expNode;
expNode *p;
int expNum;
int expType; //0 for integers, 1 for decimals, 2 for fractions
int oprNum;
int oprType[];
int min, max;
int accuracy; //the accuracy of decimals public:
arithmetic() { expNum = ;
expType = ;
oprNum = ;
//oprType = 0;
oprType[] = ;
oprType[] = ;
oprType[] = ;
oprType[] = ;
oprType[] = ; min = ;
max = ;
accuracy = ; } void setExpNum(int n);
void setExpType(int n);
void setOprNum(int n);
void setOprAdd(int n);
void setOprSub(int n);
void setOprMul(int n);
void setOprDiv(int n);
void setOprPow(int n);
void setOprAll(int a, int s, int m, int d, int p);
void setOprByStr(string s); void setBounds(int min, int max);
void setAccuracy(int n); int getExpNum(); string* getExpSet(); string* getAnsSet(); void generate(); };
class arithmetic
(a)类的成员变量
(b)类的成员函数
(c)使用示例
arithmetic test; //定义一个arithmetic类变量 test.setExpNum(); //生成20个表达式test.setExpType(DECIMAL); //表达式类型为小数
test.setBounds(, ); //操作数范围为1~20
test.setOprNum(); //运算符为4个
test.setOprAdd();
test.setOprSub();
test.setOprMul();
test.setOprDiv();
test.setOprPow(); //运算符包括“+-*/”
/*设置运算符种类还可用以下两种方式之一
test.setOprAll(1, 1, 1, 1, 0);
test.setOprByStr("+-*/"); */
test.setAccuracy(); //精度为两位小数 test.generate(); //生成表达式 string* expSet; //表达式数组
string* ansSet; //结果数组
expSet = test.getExpSet();
ansSet = test.getAnsSet(); for (int i = ; i < test.getExpNum(); i++) {
//屏幕输出表达式及结果
cout << expSet[i] << " = " << ansSet[i] << endl;
}
(2)设置参数(set系列函数)
函数内部定义了异常,当传入参数不符合要求时,抛出异常,并将题目数量expNum设为0,防止程序崩溃。
void arithmetic::setExpNum(int n) { try {
expNum = n;
if (expNum < ) {
throw "The number of expressions must be an integer and at least one.";
}
}
catch (const char* msg) {
cout << msg << endl;
setExpNum();
}
}
for example
(3)随机生成表达式
generate函数根据表达式的种类expType调用不同的生成表达式的函数。
调用的函数原型:
string newExactDivExp(int oprNum, int oprType, int min, int max, int &result); //返回除法均为整除的表达式,result保存结果字符串
string newExp(int oprNum, int oprType, int min, int max, double &result,int accuracy); //生成结果为小数的表达式,result保存结果字符串
string newFracExp(int oprNum, int oprType, int min, int max, Fraction &result); //生成分数表达式,result保存结果字符串
其中newFracExp函数使用了自定义的Fraction类,其中重载了各种运算符,类定义如下:
class Fraction
{
private:
int nume; // numerator
int deno; // denominator
public:
Fraction(int nu = , int de = ) :nume(nu), deno(de) {}
void simplify();
string display();
void setNume(int nu);
void setDeno(int de);
int getNume();
int getDeno(); friend Fraction operator+(const Fraction &c1, const Fraction &c2);
friend Fraction operator-(const Fraction &c1, const Fraction &c2);
friend Fraction operator*(const Fraction &c1, const Fraction &c2);
friend Fraction operator/(const Fraction &c1, const Fraction &c2);
friend Fraction operator^(const Fraction &c1, const Fraction &c2); friend bool operator>(const Fraction &c1, const Fraction &c2);
friend bool operator<(const Fraction &c1, const Fraction &c2);
friend bool operator==(const Fraction &c1, const Fraction &c2);
friend bool operator!=(const Fraction &c1, const Fraction &c2);
friend bool operator>=(const Fraction &c1, const Fraction &c2);
friend bool operator<=(const Fraction &c1, const Fraction &c2); friend Fraction operate(const Fraction &c1, char c, const Fraction &c2);
};
class Fraction
测试结果
以下expNum = 20, oprNum = 4,运算符包括“+-*/^”, min = 1, max = 20。
(1)生成整除表达式(expType = 0)
(2)生成一般(非整除)表达式(expType = 1,accuracy = 2)
(3)生成分数表达式(expType = 2)
BUG记录与分析
这部分主要记录一些编程时出现的bug,大多数都是一些细节上的问题。
(1)生成随机数
在这一部分我们还是遇到了很多小问题, 首先就是第一次测试时,发现生成两个操作数经常相等, 后来才发现是因为这句“srand((unsigned) time(NULL));”不能放在我们的random函数里面; 再就是一个double转int的问题, 因为我们计算中间结果是用double类型来计算, 然后定义的变量为int型, 然后出现了结果为负数的情况, 原因可能是double转int丢失了小数, 导致后续计算结果并非准确值。
(2)情况考虑不全
这个主要是在生成表达式函数内部, 为了使表达式更随机, 我们使用了随机函数来决定新产生的随机数是放在左还是放在右, 但有一些情况是必须放在左或者右的, 但是由于初始考虑不周, 就出现了很多非预期的结果。
这里总结一下, 必须放在左边的情况: 运算符为除号, 且tempVal(当前中间结果)为0; 运算符为减号, 且tempVal 大于范围最大值max。必须放在右边的情况: 运算符为减号, 且tempVal 小于 范围最小值min。
再就是关于是否加括号的问题,开始只考虑了运算符的优先级,没考虑减号和除号后面的括号有括号无括号要变号的问题,这导致最后结果与表达式不匹配。
(3)分数比较大小
当分母范围比较大的时候,生成的分数表达式偶尔仍会出现负数的结果,但是按代码的逻辑却并不可能出现这样的结果,开始百思不得其解,一味在代码逻辑上考虑毫无结果,最后才发现是在分数比较上出现了溢出现象,因为当时写分数比较的函数时,考虑了负数情况,为了让代码更简洁,就采取了“(通分后分子a-通分后分子b)*通分后的分母 > 0"来判断分数a大于分数b,结果没想到乘积过大导致溢出变成负数,所以说代码找bug还是不要总以为你以为是对的的地方都是对的。
DLL封装
dll封装这一步开始尝试时也是遇到了很多麻烦,要么是无法重新生成解决方案(报很多错),要么就是生成了.dll、.lib文件后,在新的项目里无法调用。最初还怀疑是不是由于使用了c++的stl,或者有多个类,类里又有友元函数等比较复杂的东西(因为教程里的通常都很简单),后来才发现还是问题出现在一些小问题上(不过不得不说有些教程确实不负责任...)。
用visual studio来进行dll封装还是很方便的,可以新建一个dll项目,添加.cpp、.h文件,将代码复制进去,然后关键就是要在.h文件中在那些需要导出的的类或者函数前加上“__declspec(dllexport)”,这一点很多教程都说到了,但很多却没说生成新的解决方案后,使用.h文件时要把里面的这一句改为“__declspec(dllimport)”,导致调用时显示“无法解析的外部函数”。
为了避免修改的麻烦,也可以用条件编译来实现,在 .h文件中加入以下代码:
#ifndef ArithmeticDLL
#define ArithmeticAPI __declspec(dllexport)
#else
#define ArithmeticAPI __declspec(dllimport)
#endif
然后在.cpp文件中加入“#define ArithmeticDLL”这一句,这样在你的项目中就是“__declspec(dllexport)”,在别的使用dll的项目中就是“__declspec(dllimport)”。
还有一个问题就是最好所有的函数、类前面都要加这一句,包括你要开放给外部调用的和你的代码内部调用的,不然会报错。另外,类加了这一句后内部的成员函数就不用加了,但它的友元函数仍然要加。
添加语句示例如下:
class ArithmeticAPI Fraction
{
private:
int nume; // numerator
int deno; // denominator
public:
Fraction(int nu = , int de = ) :nume(nu), deno(de) {} void simplify();
string display();
void setNume(int nu);
void setDeno(int de);
int getNume();
int getDeno(); ArithmeticAPI friend Fraction operator+(const Fraction &c1, const Fraction &c2);
ArithmeticAPI friend Fraction operator-(const Fraction &c1, const Fraction &c2);
ArithmeticAPI friend Fraction operator*(const Fraction &c1, const Fraction &c2);
ArithmeticAPI friend Fraction operator/(const Fraction &c1, const Fraction &c2);
ArithmeticAPI friend Fraction operator^(const Fraction &c1, const Fraction &c2); Fraction operator+();
Fraction operator-(); ArithmeticAPI friend bool operator>(const Fraction &c1, const Fraction &c2);
ArithmeticAPI friend bool operator<(const Fraction &c1, const Fraction &c2);
ArithmeticAPI friend bool operator==(const Fraction &c1, const Fraction &c2);
ArithmeticAPI friend bool operator!=(const Fraction &c1, const Fraction &c2);
ArithmeticAPI friend bool operator>=(const Fraction &c1, const Fraction &c2);
ArithmeticAPI friend bool operator<=(const Fraction &c1, const Fraction &c2); ArithmeticAPI friend Fraction operate(const Fraction &c1, char c, const Fraction &c2);
};
dll添加语句示例
重新生成解决方案后就会生成.dll、.lib文件了。
vs中使用方法如下:
1)工程中存在cpp的情况下,修改项目属性:属性--C/C++--预处理器--预处理器命令--添加_CRT_SECURE_NO_WARNINGS(由于使用了sprintf函数);
2)将7_ArithmeticDll.dll,7_ArithmeticDll.lib以及7_ArithmeticDll.h三个文件 复制到存放即将调用core的.cpp的文件夹中;
3)在头文件中添加 现有项 "7_ArithmeticDll.h";
4)在资源文件中 添加 现有项 "7_ArithmeticDll.lib”;
5)在用来调用core的.cpp中添加 #include"7_ArithmeticDll.h";
6)release/debug、x64/x86不能混用。
团队分工&结对编程的意义
结对编程作业大部分的时间都是采取共同编写代码,即“一个做驾驶员,一个做领航员”,驾驶员负责敲键盘,领航员在一侧提供建议、检查错误或帮忙搜索相关的资料。
就这次团队项目而言,我们合作还是很愉快的。清明节的前两天几乎整天都在写代码,也基本上完成了大部分的代码架构。
我(涂)主要是负责calculate函数(不过这个函数后来发现用不上)以及Fraction这个类的编写(编写指做“驾驶员”),而吕佳玲主要是对生成新的表达式的子函数来进行编写。写完后的调试等过程,则是共同完成。
完成大概架构及基本测试后,后面几天主要就是针对一些小问题及一些新出现的要求进行编写,如更改生成随机分数的方式、增加生成整除表达式的功能等等。当然现在可能还是会有一些小问题,我们这边希望通过UI使用后的反馈再进行集中调整。
通过这次作业,我们也体会到了结对编程的优越性。想起第一天晚上讨论时两个人的二脸懵逼, 如果一个人面对这样一个项目, 难度可想而知。结对编程时,出现一个问题时,两个人一起讨论总是能比一个人死磕能更快得出结果。个人编程时,难免会遇上一些“坎”而被绊住,而两个人一起编程时,每个人的“坎”可能不一样,这样一来,“坎”出现的几率也就大大降低了。而且,结对编程时不是一个人整天坐在电脑前敲键盘,时不时更换驾驶员和领航员的身份,大脑没那么容易死机,这也算是一个好处吧。
PSP表格
Status |
Stages |
预估耗时/min |
实际耗时/min |
Accept |
【计划】 Planning |
100 |
120 |
Accept |
——估计时间 Estimate |
100 |
120 |
Accept |
【开发】 Development |
1790 |
2155 |
Accept |
——需求分析 Analysis |
10 |
10 |
Accept |
——设计文档 Design Spec |
40 |
40 |
Accept |
——设计复审 Design Review |
10 |
10 |
Accept |
——代码规范 Coding Standard |
10 |
5 |
Accept |
——具体设计 Design |
120 |
150 |
Accept |
——具体编码 Coding |
1440 |
1600 |
Accept |
——代码复审 Code Review |
40 |
40 |
Accept |
——测试 Test |
120 |
300 |
Accept |
【记录用时】 Record Time Spent |
20 |
10 |
Accept |
【测试报告】 Test Report |
60 |
40 |
Accept |
【算工作量】 Size Measurement |
20 |
15 |
Accept |
【总结改进】 Postmortem |
60 |
120 |
Accept |
【合计】 Summary |
2050 |
2460 |
课程建议
对于课程来说,最主要的建议还是希望能够在发布作业的时候就把详细要求说清楚,这个应该不难吧,很多东西只要想得全面一点就会发现很多不清不楚的问题。动不动就在群里或者博客里加一句真的很不友好啊。虽然还不至于“上面一张嘴,下面跑断腿”,但是还是很难受的。
还一点就是觉得这门课把大家都当成未来当码农(过于面向就业)也不是很友好。当然编程能力对于信院来说当然非常重要,但是无论怎么说,这门课毕竟还是一门课,虽然能学到很多东西,但代价就是每周在上面花的时间比别的所有课的时间加起来还多。现在结对项目结束了,要继续开启团队项目,我们首先要花很多时间去积累所需的知识(基本是从零开始),还要去看书写读书笔记,还有课后作业,每一项都不是每周一两个小时就能搞完的(可能我比较慢吧)。其实我觉得上学期电设二团队项目的模式挺好的,每周向助教汇报进展,助教提供建议,同时制定下周的目标。当然可能软工更高级一点、范围更广一些,性质也不一样,不过有些方面还是能借鉴一下的吧。
对团队项目的启发
团队项目即将开始,从这次结对编程的经验来看,还是要先完成大的架构,先出来一个能用的再说,细节上的东西可以不断地去补充,这也是“敏捷原则”的体现之一吧。还有就是细节方面、鲁棒性方面还是要尽量考虑的全面一点。再就是像我们core组是要提供接口给UI组使用,这就涉及到一个用户体验的问题,用户体验永远是应该摆在第一位的。
结对作业——随机生成四则运算(Core 第7组)的更多相关文章
- 结对编程项目报告--四则运算CORE
<!doctype html> sw_lab2.mdhtml {overflow-x: initial !important;}#write, body { height: auto; } ...
- 基于c编写的关于随机生成四则运算的小程序
基于http://www.cnblogs.com/HAOZHE/p/5276763.html改编写的关于随机生成四则运算的小程序 github源码和工程文件地址:https://github.com/ ...
- (幼儿园毕业)Javascript小学级随机生成四则运算
软件工程第二次结对作业四则运算自动生成器网页版 一.题目要求 本次作业要求两个人合作完成,驾驶员和导航员角色自定,鼓励大家在工作期间角色随时互换,这里会布置两个题目,请各组成员根据自己的爱好任选一题. ...
- 用Java随机生成四则运算
代码链接:https://github.com/devilwjy/Code.Demo 需求分析: 1.程序可接收一个输入参数n,然后随机产生n道加减乘除练习题,每个数字在 0 和 100 之间,运算符 ...
- 软件工程课堂作业(二)续——升级完整版随机产生四则运算题目(C++)
一.设计思想: 1.根据题目新设要求,我将它们分为两类:一类是用户输入数目,根据这个数目改变一系列后续问题:另一类是用户输入0或1,分情况解决问题. 2.针对这两类要求,具体设计思路已在上篇博文中写出 ...
- 20165236 2017-2018-2 《Java程序设计》结对编程练习_四则运算
20165236 2017-2018-2 <Java程序设计>结对编程练习_四则运算 结对小组:叶佺.郭金涛 一.需求分析: 1.能随机生成n道四则运算题目,n由使用者输入: 2.支持多种 ...
- 随机生成四则运算式2-NEW+PSP项目计划(补充没有真分数的情况)
PS:这是昨天编写的随机生成四则运算式2的代码:http://www.cnblogs.com/wsqJohn/p/5264448.html 做了一些改进. 补:在上一次的运行中并没有加入真分数参与的运 ...
- 【第二次个人作业】结对作业Core第一组:四则运算生成PB16061082+PB16120517
[整体概况] 1.描述最终的代码的实现思路以及关键代码. 2.结对作业两个人配合的过程和两个人分工. 3.API接口文档和两个组的对接. 4.效能分析,优化分析和心得体会. [代码实现] 一. 实现功 ...
- Core 第三组 结对作业——四则运算 Part1. Core代码编写
结对作业——四则运算 Part1. Core代码编写 PB15061303 刘梓轩PB16061489 艾寅中 GITHUB 地址 戳这里 目录 (因为内容较多,分为了三个部分,但作业系统中只能提交一 ...
随机推荐
- 友盟在部分手机上在进程被kill的情况下接收不到推送的问题
app集成友盟推送后就能接收推送消息,即使在进程被kill的情况下也能接收.这个因为友盟有长连互保,用户设备中任何一个集成过友盟推送的app打开,即使他的app没打开也能启动push service, ...
- android的几种“通知”方式简单实现(Notification&NotificationManager)
关于通知Notification相信大家都不陌生了,平时上QQ的时候有消息来了或者有收到了短信,手机顶部就会显示有新消息什么的,就类似这种.今天就稍微记录下几种Notification的用法.3.0以 ...
- 分区助手官网使用教程(专业版、绿色版和WinPE版)(图文详解)
不多说,直接上干货! 详情见 http://www.disktool.cn/jiaocheng/index.html http://www.disktool.cn/jiaocheng/index2.h ...
- redis 迁移工具 redis-port 从阿里云迁移到aws
对于 redis 的 迁移我在网上看到了很多方法,有使用redis-dump 的,有使用 aof导入方式,有rdb文件迁移方式,和redis-port. 由于我是将 redis 从阿里云迁移到AW ...
- springboot-25-springboot 集成 ActiveMq
消息的发布有2种形式, 队列式(点对点) 和主题式(pub/sub) 模式, 队列式发布后, 接收者从队列中获取消息后, 消息就会消失, 但任意消费者都可以从队列中接受消息, 消息只能被接受一次 主题 ...
- 使用httpClient处理get请求或post请求
另外一个版本: http://www.cnblogs.com/wenbronk/p/6671928.html 在java代码中调用http请求, 并将返回的参数进行处理 get请求: public s ...
- Java中的四种引用
引用定义 实际上,Java中存在四种引用,它们由强到弱依次是:强引用.软引用.弱引用.虚引用.下面我们简单介绍下这四种引用: 强引用(Strong Reference):通常我们通过new来创建一个新 ...
- MySQL中一个sql语句包含in优化问题
第一版sql: SELECT module.id, module.module_name, module.module_code `module` where IN (module.did_acces ...
- favi.icon是什么?
各大网站都有属于自己的图标 概念解释: ico是Icon file的缩写,是Windows的图标文件格式的一种 1.制作ico格式的图片一个上传到服务器的根目录图片保存为favicon.ico 2.制 ...
- Echart 改变X轴、Y轴、折线的颜色和数值
在操作E-chart时需要根据需求改变颜色和属性 图1: option = { xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu' ...