项目 | 内容 |
---|---|
课程连接 | 2020春季计算机学院软件工程(罗杰 任健) |
做业要求 | 结对项目做业 |
课程目标 | 系统学习软件开发理论和流程,经过实践积累软件开发经验 |
本博客的收获 | 开发了一个简单带有UI的项目,总结结对编程过程的经历和优缺点 |
教学班级 | 005 |
项目地址 | https://github.com/zwx980624/IntersectProject |
UI地址 | https://github.com/zwx980624/IntersectionGUI |
队友地址 | xgnb |
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务须要多少时间 | 30 | 20 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 720 | 600 |
· Design Spec | · 生成设计文档 | 60 | 60 |
· Design Review | · 设计复审 (和同事审核设计文档) | 60(同时) | 60(同时) |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
· Design | · 具体设计 | 80 | 60 |
· Coding | · 具体编码 | 600 | 500 |
· Code Review | · 代码复审 | 600(同时) | 500(同时) |
· Test | · 测试(自我测试,修改代码,提交修改) | 300 | 400 |
Reporting | 报告 | ||
· Test Report | · 测试报告 | 30 | 30 |
· Size Measurement | · 计算工做量 | 30 | 30 |
· Postmortem & Process Improvement Plan | · 过后总结, 并提出过程改进计划 | 240 | 300 |
合计 | 2110 | 2020 |
information Hiding 是指信息的隐藏和封装python
In computer science, information hiding is the principle of segregation of the design decisions in a computer program that are most likely to change, thus protecting other parts of the program from extensive modification if the design decision is changed. The protection involves providing a stable interface(computer_science) which protects the remainder of the program from the implementation (the details that are most likely to change).c++
从维基百科中,咱们能够看出information Hiding是指外部人员在能够调用接口而没必要关心内部的实现,即便内部实现发生变更,对外部人员也不会产生任何影响。在咱们设计一些接口时,就采用了这种思想。好比处理输入的接口能够接受文件、字符串、标准输入等任意输入流,调用接口者不须要知道咱们内部是怎么处理的,就能够完成对输入图形的处理和保存。还有GUI使用的guiProcess接口,只须要传入字符串和保存交点的vector,不须要额外构建该接口内部使用的各类类,便可完成交点计算。git
interface Design 指接口设计,包含单一职责原则、里氏替换原则、依赖倒置原则、接口隔离原则、最少知识原则、开闭原则等。github
咱们根据以上原则的一部分,设计了以下两个计算核心的接口:正则表达式
guiProcess
算法
函数传入符合格式的字符串,交点集用指针返回,交点个数用返回值返回。编程
std::pair<double,double>
表示,保证调用者清晰理解。cmdProcess
为了命令行调用方便,采用需求规定的命令行输入格式进行输入输出canvas
loose Coupling是松耦合设计模式
In computing and systems design a loosely coupled system is one in which each of its components has, or makes use of, little or no knowledge of the definitions of other separate components. Subareas include the coupling of classes, interfaces, data, and services.数据结构
咱们的dll在抛出异常时会将错误信息存储为标准异常,别人在调用时,不须要专门使用咱们设计的异常类,就能够捕捉异常,能够减少耦合度。
unordered_set
和unordered_map
,CSlope要实现Hash方法
和等于运算符
。Hash方法
和等于运算符
。inputShapes: 处理输入函数,直线、射线、线段按斜率分组,放到map<double, set<CLine>>
的_k2lines
中,圆直接放到set<CCircle>
的 _circles
里。
新增:上一次需求中直线和直线平行不可能产生有限交点,可是这次需求新增的线段和射线就可能产生共线相交在端点的状况,是符合需求说明书的状况,须要特殊考虑。在此函数中实现,后续就能够正常按照平行分组计算了。
calcShapeInsPoint:求两个图形交点的函数,分三种状况,返回点的vector。
cntTotalInsPoint: 求全部焦点的函数,按先直线后圆的顺序依次遍历求焦点。已经遍历到的图形加入一个over
集中。
over
集中其它不平行直线。_insp2shapes
这个map<CPoint, vector<CShape>>
数据结构,为交点到通过它的线集的映射。_insp2shapes
里_insp2shapes.size()
即为交点个数。具体说明本次需求新增的重要代码
1. 判断求交公式算出的交点在射线、线段范围内
// require: cx cy belongs to the line // return: if point in shape range bool CLine::crossInRange(double cx, double cy) const { if (type() == "Line") { // 统一接口,直线返回true return true; } else if (type() == "Ray") { // 射线 if (k().isInf()) { // 斜率无穷,比较y值,dcmp为浮点数比较函数,定义见下 if (dcmp(cy, y1())*dcmp(y2(), y1()) != -1) { return true; } } else { // 正常状况,比较x if (dcmp(cx, x1())*dcmp(x2(), x1()) != -1) { return true; } } return false; } else { // 线段 ... //相似于射线,代码略 } } // 浮点数比较函数,相等返回0,前者大返回1,不然返回-1 #define EPS 1e-10 int dcmp(double d1, double d2) { if (d1 - d2 > EPS) { return 1; } else if (d2 - d1 > EPS) { return -1; } else { return 0; } }
浮点数因为有浮点偏差,其hash值将不一样,因此应该截取某精度,转换成整形进行hash。
#define COLLISION 100000. class SlopeHash { public: std::size_t operator()(const CSlope& s) const { // 乘精度,四舍五入,转整形,算Hash unsigned long long val = dround2ull(s.val() * COLLISION); return std::hash<bool>()(s.isInf()) + (std::hash<unsigned long long>()(val) << 16); } };
咱们随机生成了8000条几何图形数据用于性能测试,其中直线、线段、射线、圆各2000个,最终计算获得交点数为18175002个交点。随机数生成模块为python中random包。
VS2019中使用性能探查器,分析CPU利用率以下:
能够看出,耗费时间最多的函数,是计算输入全部图形的交点总数的函数cntTotalInsPoint
,进入函数入内部查看分析结果:
与我的项目相似,耗费时间最多的仍然是记录交点的数据结构set
对交点的插入。因为已经使用unordered_set
相比于本来的set
时间复杂度已经低了不少,所以固有的数据结构维护时间不可避免。其次咱们还注意到计算两个图形间交点的函数calcShapeInsPoint
占用了较大的时间开销。进入该函数中查看分析结果:
能够看出判断点是否在线段或射线上,花费时间较多,判断点是否在交点上的函数,内部是这样的:
仔细想一想后,发现其实彻底没有必要再这个函数内部再判断一次点是否在线端或射线所处直线上,由于咱们自己会用到这个函数的场景,就是先计算出线段或射线所在直线与其余直线的交点,再判断交点是否在线段或射线上,所以,这个判断属于画蛇添足,是一个能够优化的点。
删除点是否在线上的判断后,再次使用性能分析,结果以下:
能够发现总时间下降了4~5s,再次进入calcShapeInsPoint
函数中查看
能够发现crossInRange
函数已经再也不是花费最多的部分,说明仍是颇有效果的。其他部分优化也都相似,不断对比分析,删除冗余计算结果。
Design by Contract,也称契约式设计:
It prescribes that software designers should define formal, precise and verifiable interface specifications for software components, which extend the ordinary definition of abstract data types with preconditions, postconditions and invariants. These specifications are referred to as "contracts", in accordance with a conceptual metaphor with the conditions and obligations of business contracts.
简单来讲,就是为每一个接口写好各类使用的规范,包括调用前的规范和调用后的结果规范。在曾经的OO课上,咱们接触并学习过写函数的约束和规范,我的感受优势和缺点以下:
优势:经过书写前置条件、后置条件和不变式这种逻辑化的语言,能够避免开发人员对模块的实现产生歧义,其次经过合理的设计约束,可让模块的耦合度更低,更加符合上述提到的三个设计原则。
缺点:须要花费大量时间,而且对开发人员的素质要求高。好比本次做业过程当中,咱们就没有严格使用标准的前置、后置语句,而是使用通俗化的语言描述,不然又是一笔巨大的时间开销。
咱们的pipeline函数就采用了契约式设计模式,为了提升效率简化过程,咱们采用了比较书面化的语言来描述规范,而没有严格遵循标准得contract格式。如如下几个函数:
// calculate Intersections of one circ and one line // need: para1 is CCirc, para2 is CLine // return a vector of intersections. size can be 0,1,2. std::vector<CPoint> calcInsCircLine(const CShape& circ, const CShape& line) { ... }
// calculate all intersect points of s1 and s2 // return the points as vector // need: s1, s2 should be CLine or CCircle. // special need: if s1, s2 are CLine. They cannot be parallel. std::vector<CPoint> CIntersect::calcShapeInsPoint(const CShape& s1, const CShape& s2) const { ... }
// the main pipeline: loop the inputs and fill in _insp2shapes or _insPoints // return the total count of intersect points // need: _k2lines and _circles have been filled int CIntersect::cntTotalInsPoint() { ... }
在设计时咱们都是先写出契约,以后的代码实现中严格遵照契约中的需求,好比使用calcShapeInsPoint
函数时就必须遵照下面需求,不然将产生错误。
// special need: if s1, s2 are CLine. They cannot be parallel.
首先展现使用OpenCppCoverage
插件测试获得代码覆盖率为:
其中部分Uncovered line
是因为VS强大的编译优化,把一些函数在内联处理了,因此执行不到。
在询问了一些同窗而且到网上查询以后,咱们仍是没有找到如何直接将VS的单元测试项目加入OpenCppCoverage
的代码覆盖检测中,所以咱们选择将单元测试代码手动从单元测试项目中移入主函数中。主要测试结构以下:
int main() { ... ... //从单元测试项目中转移至此的单元测试1 ... //从单元测试项目中转移至此的单元测试2 ... }
单元测试中,主要包括文件读写的测试,对一些关键函数如计算交点等的测试,对异常的测试。
部分单元测试代码展现以下:
测试图形之间交点的计算:该部分比较繁杂,须要考虑的状况有如下几种,我的项目中已经出现过的就再也不放测试代码,与上次相似
直线与直线仅有一个交点、没有交点,其中须要包括直线斜率不存在、为0、其余的状况
直线与圆有两个交点、一个交点、没有交点,其中须要包括直线斜率不存在、为0、其余的状况
直线与线段有一个交点、没有交点,其中须要包括直线与线段平行、不平行有一个交点、不平行没有交点的状况。如下例子为一个交点的状况:
CLine t1 = CLine(0, 2, 0, 0, "Seg"); CLine t2 = Cline(3, 2, 4, 2); ans = ins.calcShapeInsPoint(t1, t2); Assert::AreEqual(1, (int)ans.size()); Assert::AreEqual(true, CPoint(0, 2) == ans[0]);
直线与射线有一个交点、没有交点,其中须要包括直线与射线平行、不平行有一个交点、不平行没有交点的状况。如下例子为没有交点的状况:
CLine t1 = CLine(0, 2, 0, 0, "Ray"); CLine t2 = Cline(3, 2, 3, 4); ans = ins.calcShapeInsPoint(t1, t2); Assert::AreEqual(0, (int)ans.size());
圆与圆有两个交点、一个交点、没有交点,其中须要包括外离、外切、相交、内切、内含的状况
圆与线段有两个交点、一个交点、没有交点,其中须要包含线段所在直线与圆相离、相切、相交以及不一样交点数的状况。如下例子为两个交点的状况:
CCircle c = CCircle(0, 0, 2); CLine t1 = Cline(2, 0, 0, 2, "Seg"); ans = ins.calcShapeInsPoint(t1, c); Assert::AreEqual(2, (int)ans.size()); Assert::AreEqual(true, CPoint(0, 2) == ans[0]); Assert::AreEqual(true, CPoint(2, 0) == ans[1]);
圆与射线有两个交点、一个交点、没有交点,其中须要包含射线所在直线与圆相离、相切、相交以及不一样交点数的状况。如下例子为一个交点的状况:
CCircle c = CCircle(0, 0, 2); CLine t1 = Cline(0, 0, 0, 2, "Ray"); ans = ins.calcShapeInsPoint(c, t1); Assert::AreEqual(1, (int)ans.size()); Assert::AreEqual(true, CPoint(0, 2) == ans[0]);
线段与线段有一个交点、没有交点,其中须要包含两个线段所在直线之间的各类关系以及不一样交点数的状况,须要特别考虑线段与线段共线时端点重合的状况。如下例子为共线时一个交点的状况:
CLine t1 = Cline(0, 0, 0, 2, "Seg"); CLine t2 = Cline(0, -2, 0, 0, "Seg"); ans = ins.calcShapeInsPoint(t1, t2); Assert::AreEqual(1, (int)ans.size()); Assert::AreEqual(true, CPoint(0, 0) == ans[0]);
线段与射线有一个交点、没有交点,其中须要包含线段与射线所在直线之间的各类关系以及不一样交点数的状况,须要特别考虑线段与射线共线时端点重合的状况。如下例子为共线时一个交点的状况:
CLine t1 = Cline(0, 2, 0, 0, "Seg"); CLine t2 = Cline(0, 0, 0, -2, "Ray"); ans = ins.calcShapeInsPoint(t1, t2); Assert::AreEqual(1, (int)ans.size()); Assert::AreEqual(true, CPoint(0, 0) == ans[0]);
射线与射线有一个交点、没有交点,其中须要包含两个射线所在直线之间的各类关系以及不一样交点数的状况,须要特别考虑射线与射线共线时端点重合的状况。如下例子为共线时没有交点的状况:
CLine t1 = Cline(0, 0, 2, 0, "Ray"); CLine t2 = Cline(-1, 0, -2, 0, "Ray"); ans = ins.calcShapeInsPoint(t1, t2); Assert::AreEqual(0, (int)ans.size());
交点数统计测试:测试各类状况下,如不一样图形之间有重合交点、图形数量不多、图形数量不少等,交点统计是否正确。举例:
TEST_METHOD(TestMethod10) { ifstream fin("../test/test10.txt"); if (!fin) { Assert::AreEqual(132, 0); } CIntersect ins; ins.inputShapes(fin); int cnt = ins.cntTotalInsPoint(); Assert::AreEqual(433579, cnt); }
异常单元测试:对各类异常状况的单元测试,主要是经过构造异常数据传入输入函数,捕捉其抛出的异常与预期异常的信息进行比较,将在第八章中详细说明,这里仅放出几个样例。
测试射线与射线重合的状况(其中之一)
TEST_METHOD(TestMethod_RR1) { string strin = "2\nR 0 0 5 5\nR 6 6 -1 -1\n"; ShapeCoverException s(2, "R 6 6 -1 -1", "R 0 0 5 5"); InputHandlerException std = s; istringstream in(strin); if (!in) { Assert::AreEqual(132, 0); } CIntersect ins; try { ins.inputShapes(in); Assert::AreEqual(132, 0); } catch (InputHandlerException e) { Assert::AreEqual(true, strcmp(std.what(), e.what()) == 0); } }
测试射线与线段重合的状况(其中之一)
TEST_METHOD(TestMethod_SR1) { string strin = "3\nS 0 0 0 4\nS 0 0 4 0\nR 0 -2 0 -1"; ShapeCoverException s(3, "R 0 -2 0 -1", "S 0 0 0 4"); InputHandlerException std = s; istringstream in(strin); if (!in) { Assert::AreEqual(132, 0); } CIntersect ins; try { ins.inputShapes(in); Assert::AreEqual(132, 0); } catch (InputHandlerException e) { Assert::AreEqual(true, strcmp(std.what(), e.what()) == 0); } }
测试线段与线段重合的状况(其中之一)
TEST_METHOD(TestMethod_SS1) { string strin = "3\nS 0 -1 0 1\nS 0 3 0 6\nS 0 0 0 2\n"; ShapeCoverException s(3, "S 0 0 0 2", "S 0 -1 0 1"); InputHandlerException std = s; istringstream in(strin); if (!in) { Assert::AreEqual(132, 0); } CIntersect ins; try { ins.inputShapes(in); Assert::AreEqual(132, 0); } catch (InputHandlerException e) { Assert::AreEqual(true, strcmp(std.what(), e.what()) == 0); } }
计算模块异常处理大体分类以下,均继承c++标准异常:
异常类的UML图以下:
关键异常详细介绍:
ShapeNumberException
当没法从输入流中读入N、读入的N范围不符合规范、N与实际输入的图形数量不匹配时,抛出该异常。
该异常构造方式与标准异常相同,传入错误信息字符串,可使用what()
函数获取错误信息,获取到的错误信息与传入错误信息相同。如:
单元测试举例以下:
TEST_METHOD(TestMethod_N6) { string strin = "1\nL 1 2 3 4\nC 1 1 2"; ShapeNumberException s("The number of graphics is larger than N."); InputHandlerException std = s; istringstream in(strin); if (!in) { Assert::AreEqual(132, 0); } CIntersect ins; try { ins.inputShapes(in); Assert::AreEqual(132, 0); } catch (InputHandlerException e) { Assert::AreEqual(true, strcmp(std.what(), e.what()) == 0); } }
IllegalFormatException
当读入过程当中某行既不是空行、也不符合规定的输入格式即(L <x1> <y1> <x2> <y2>
、R <x1> <y1> <x2> <y2>
、S <x1> <y1> <x2> <y2>
、C <x> <y> <r>
四种格式中的一种)时,抛出该异常。
该异常构造方式为,传入格式错误的图形序号和该行字符串,可使用what()
函数获取错误信息,获取到的错误信息为序号、字符串以及相应修改建议。如:
单元测试举例以下:
TEST_METHOD(TestMethod_ILLShape) { string strin = "1\n\nL 1 2 3 F\n"; IllegalFormatException s(1,"L 1 2 3 F"); InputHandlerException std = s; istringstream in(strin); if (!in) { Assert::AreEqual(132, 0); } CIntersect ins; try { ins.inputShapes(in); Assert::AreEqual(132, 0); } catch (InputHandlerException e) { Assert::AreEqual(true, strcmp(std.what(), e.what()) == 0); } }
OutRangeException
当读入图形信息时,图形的参数超过规定的范围,抛出该异常。
该异常构造方式为,传入参数超过规定范围的图形序号和该行字符串,可使用what()
函数获取错误信息,获取到的错误信息为序号、字符串以及相应修改建议。如:
单元测试举例以下:
TEST_METHOD(TestMethod_OutRange) { string strin = "1\n\nC 1 1 0\n"; OutRangeException s(1, "C 1 1 0"); InputHandlerException std = s; istringstream in(strin); if (!in) { Assert::AreEqual(132, 0); } CIntersect ins; try { ins.inputShapes(in); Assert::AreEqual(132, 0); } catch (InputHandlerException e) { Assert::AreEqual(true, strcmp(std.what(), e.what()) == 0); } }
ShapeRepeatedException
当读入某一图形,发现与已有图形彻底相同时,抛出该异常。
该异常构造方式为,传入出现重复的图形序号和该行字符串,可使用what()
函数获取错误信息,获取到的错误信息为序号、字符串以及相应修改建议。如:
单元测试举例以下:
TEST_METHOD(TestMethod_ShapeRe) { string strin = "2\n\nC 1 1 1\nC 1 1 1\n"; ShapeRepeatedException s(2, "C 1 1 1"); InputHandlerException std = s; istringstream in(strin); if (!in) { Assert::AreEqual(132, 0); } CIntersect ins; try { ins.inputShapes(in); Assert::AreEqual(132, 0); } catch (InputHandlerException e) { Assert::AreEqual(true, strcmp(std.what(), e.what()) == 0); } }
IllegalLineException
当读入直线、线段、射线,且发现描述线类图形的两个点重合时,抛出该异常。
该异常构造方式为,传入端点重合的图形序号和该行字符串,可使用what()
函数获取错误信息,获取到的错误信息为序号、字符串以及相应修改建议。如:
单元测试举例以下:
TEST_METHOD(TestMethod_IllLine) { string strin = "2\n\nL 1 1 1 2\nL 0 1 0 1\n"; IllegalLineException s(2, "L 0 1 0 1"); InputHandlerException std = s; istringstream in(strin); if (!in) { Assert::AreEqual(132, 0); } CIntersect ins; try { ins.inputShapes(in); Assert::AreEqual(132, 0); } catch (InputHandlerException e) { Assert::AreEqual(true, strcmp(std.what(), e.what()) == 0); } }
ShapeCoverException
当读入直线、线段、射线等与已有线类图形重合(即有无限个交点)时,抛出该异常。
该异常构造方式为,传入该图形(指新读入的图形)序号、该图形字符串信息、与其重合的图形字符串信息,可使用what()
函数获取错误信息,获取到的错误信息为序号、该图形、与其重合的图形信息及相应修改建议。如:
单元测试举例以下:
TEST_METHOD(TestMethod_RS1) { string strin = "3\nS 0 0 4 0\nR 0 -2 0 -1\nS 0 0 0 4"; ShapeCoverException s(3, "S 0 0 0 4", "R 0 -2 0 -1"); InputHandlerException std = s; istringstream in(strin); if (!in) { Assert::AreEqual(132, 0); } CIntersect ins; try { ins.inputShapes(in); Assert::AreEqual(132, 0); } catch (InputHandlerException e) { Assert::AreEqual(true, strcmp(std.what(), e.what()) == 0); } }
首先给出界面模块的总体外观。看这可爱的外星人
输入面板模块:提供添加、删除、清空功能
计算核心接口:添加图形后,点击求解交点
调用计算核心模块,返回交点个数对话框,并在绘图面板中绘制交点,后文将详述接口函数的设计。
绘图面板模块:在添加图形时绘制图形,在计算交点后绘制交点。
错误反馈模块:反馈计算核心模块抛出的异常,例如
本项目的GUI采用c++的Qt库实现,对比之前使用过的MFC,Qt有易上手,跨平台,界面美观等特色。对于不多写图形程序的我来讲,采用Qt是一个很合适的选择。下面分别介绍各个模块的关键代码实现。
输入面板模块:充分利用Qt ui设计器提供的诸多功能强大的组件
void IntersectionGUI::on_actAddFile_triggered() { if (lastPath == QString::fromLocal8Bit("")) { //记录上次打开的路径 lastPath = QDir::currentPath();//获取系统当前目录 } //获取应用程序的路径 QString dlgTitle = QString::fromLocal8Bit("选择一个文件"); //对话框标题 QString filter = QString::fromLocal8Bit("文本文件(*.txt);;全部文件(*.*)"); //文件过滤器 QString aFileName = QFileDialog::getOpenFileName(this, dlgTitle, lastPath, filter); if (!aFileName.isEmpty()) { lastPath = aFileName; // 读入文件数据,文件格式与需求定义相同 std::ifstream fin(aFileName.toLocal8Bit()); int N; std::string line; fin >> N; std::getline(fin, line); while (N--) { std::getline(fin, line); QString qline = QString::fromStdString(line); if (!isShapeStrValid(qline)) { // 正则表达式简单判断输入格式 QString dlgTitle = QString::fromLocal8Bit("输入格式错误"); QMessageBox::critical(this, dlgTitle, qline); break; } draw_shape_from_str(qline); //在绘图面板上绘图 // 图形列表中添加一行 QListWidgetItem * aItem = new QListWidgetItem(); //新建一个项 aItem->setText(qline); //设置文字标签 aItem->setCheckState(Qt::Unchecked); //设置为选中状态 aItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); ui.listWidget->addItem(aItem); //增长一个项 } } }
求解交点
按钮后,调用槽函数on_actSolve_triggered
void IntersectionGUI::on_actSolve_triggered() { // 从图形列表中构造给核心接口的输入字符串 std::string input; input += std::to_string(ui.listWidget->count()) + "\n"; for (int i = 0; i < ui.listWidget->count(); i++) { QListWidgetItem *aItem = ui.listWidget->item(i);//获取一个项 QString str = aItem->text(); input += str.toStdString() + "\n"; } // 调用核心接口 std::vector<std::pair<double, double> > points; int cnt = 0; try { cnt = guiProcess(&points, input); // 调用核心接口dll函数 } catch (std::exception e) { // 反馈计算核心抛出的异常 QString dlgTitle = QString::fromLocal8Bit("计算出现错误"); QMessageBox::critical(this, dlgTitle, e.what()); return; } // 绘制交点 for (auto vit = points.begin(); vit != points.end(); ++vit) { int w, h; xy2wh((int)(vit->first), (int)(vit->second), w, h); draw_point(w, h, Qt::red); } // 反馈交点总数 QString dlgTitle = QString::fromLocal8Bit("计算结果"); QString strInfo = QString::fromLocal8Bit("交点总数为:"); strInfo += strInfo.asprintf("%d", cnt); QMessageBox::information(this, dlgTitle, strInfo,QMessageBox::Ok, QMessageBox::NoButton); }
绘图面板模块:采用QLabel和QPixmap组件进行绘图,主要的绘图函数有如下几种
最难写的函数就是绘制直线、射线了,Qt自带的绘制直线函数drawLine
其实是给定两点绘制线段,因此想要达到绘制直线和线段的效果就必须求出与画板边界的交点。先根据方向算左右方向的边界,若是发现碰的不是左右边界,再算上下边界。
void IntersectionGUI::draw_ray(int x1, int y1, int x2, int y2, QColor const c, int const w) { QPainter Painter(&curPixmap); Painter.setRenderHint(QPainter::Antialiasing, true); //反走样 Painter.setPen(QPen(c, w)); if (x2 == x1) { // 竖直 if (y2 > y1) { y2 = REAL_SIZE; } else { y2 = -REAL_SIZE; } } else if (y1 == y2) { // 水平 if (x2 > x1) { x2 = REAL_SIZE; } else { x2 = -REAL_SIZE; } } else { // 向右上倾斜 先算左右边界交点,超范围就算上下交点 double k = (double)(y2 - y1) / (x2 - x1); double b = y1 - k * x1; if (x2 > x1) { double y_ = REAL_SIZE * k + b; if (y_ > REAL_SIZE) { y2 = REAL_SIZE; x2 = (y2 - b) / k; } else if (y_ < -REAL_SIZE) { y2 = -REAL_SIZE; x2 = (y2 - b) / k; } else { x2 = REAL_SIZE; y2 = y_; } } else { ... } // 相似 } QPoint p1 = xy2whPoint(x1, y1); QPoint p2 = xy2whPoint(x2, y2); Painter.drawLine(p1, p2); ui.canvas->setPixmap(curPixmap); }
为了设计松耦合,咱们将核心部分设计了以下两个函数做为接口,命令行和GUI均可以经过这两个接口访问计算核心。
#ifdef IMPORT_DLL #else #define IMPORT_DLL extern "C" _declspec(dllimport) //指的是容许将其给外部调用 #endif IMPORT_DLL int guiProcess(std::vector<std::pair<double,double>> *points, std::string msg); IMPORT_DLL void cmdProcess(int argc, char *argv[]);
guiProcess
函数传入符合格式的字符串,交点集用指针返回,交点个数用返回值返回。cmdProcess
采用需求规定的命令行输入格式进行输入输出注:这两个函数并非分别针对gui和cmd,只是输入输出格式不一样,均可以任意调用,保证松耦合。
经过此接口将计算核心封装成动态连接库calcInterface.dll
和库calcInterface.lib
int main(int argc, char *argv[]) { typedef int (*pGui)(vector<pair<double,double>>* points, string a); typedef void (*pCmd)(int argc, char* argv[]); HMODULE hDLL = LoadLibrary(TEXT("calcInterface.dll")); vector<pair<double, double>>points; string tmp = "2\nL 1 1 0 1\nL 1 1 1 0\n"; if (hDLL) { pGui guiProcess = (pGui)GetProcAddress(hDLL, "guiProcess"); pCmd cmdProcess = (pCmd)GetProcAddress(hDLL, "cmdProcess"); try { int ans1 = guiProcess(&points, tmp); // 测试接口函数1 for (int i = 0; i < points.size(); i++) { cout << points[i].first << " " << points[i].second << endl; } cout << ans1 << endl; } catch (exception t) { cout << t.what() << endl; } cmdProcess(argc, argv); //测试接口函数2 } }
#pragma comment(lib,"calcInterface.lib") _declspec(dllexport) extern "C" int guiProcess(std::vector<std::pair<double, double>> *points, std::string msg); _declspec(dllexport) extern "C" void cmdProcess(int argc, char *argv[]); // 调用核心接口 std::vector<std::pair<double, double> > points; int cnt = 0; try { cnt = guiProcess(&points, input); // 调用核心接口dll函数 } catch (std::exception e) { // 反馈计算核心抛出的异常 QString dlgTitle = QString::fromLocal8Bit("计算出现错误"); QMessageBox::critical(this, dlgTitle, e.what()); return; }
因为单个软件存在或多或少的问题,咱们综合使用VS Live Share、腾讯会议以及github来进行远程结对编程。
VS Live Share能够更加真实的模拟两我的共同面对同一文件编程的效果,“领航员”也能够更方便的参与到代码的编写中,但VS Live Share没法让被邀请的人观看到程序的运行结果以及整个解决方案的结构。所以,咱们辅以腾讯会议共享屏幕,来观看整个项目的架构和编译、运行等信息。而后,咱们根据两我的擅长的不一样部分,经过github进行代码同步,分别在不一样的模块担任“驾驶员”和“领航员”的角色。
结对过程当中,因为两人已经在许多课程做业中创建了深厚的合做基础和友谊,互相信任,所以,在遇到一些犹豫不定的状况时,为了提升效率,在共同讨论各类方法的优劣以后,多采用断言和说服的方式肯定思路。
如下是咱们结对过程当中同时使用LiveShare和腾讯会议时的截图:
结对编程的优势:
结对编程的缺点:
结对编程的每个人的优缺点:
总结:学识渊博、代码规范、能说会道、规划合理,个人搭档的是我见过最强的搭档。
使用默认的规则。能够看到已经没有任何错误和警告。
合做小组中的两位成员:17373124 闫苗、17373299 刘紫涵
咱们与其合做,互换了计算核心dll,测试结果以下,能够正常运行,git仓库连接
这是咱们简单测试他们的dll的结果:
这是他们测试咱们dll的结果:
因为在最初设计时就已经进行过大体的商量和规划,他们采用的gui接口函数格式与咱们的不一样之处不多,只需修改接口部分少许代码便可正常对接。
IMPORT_DLL int guiProcess(std::vector<std::pair<double, double>>* points,std::vector<string> msg);