软工结对项目作业
项目 | 内容 |
---|---|
这个作业属于哪个课程 | 2020春季计算机学院软件工程(罗杰 任健) |
这个作业的要求在哪里 | 结对项目作业 |
教学班级 | 006 |
项目地址 | https://github.com/Knowden/IntersectionPlus |
一、PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 10 | 10 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 180 | 300 |
· Design Spec | · 生成设计文档 | 5 | 5 |
· Design Review | · 设计复审 (和同事审核设计文档) | 10 | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
· Design | · 具体设计 | 120 | 120 |
· Coding | · 具体编码 | 240 | 300 |
· Code Review | · 代码复审 | 60 | 60 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 60 |
Reporting | 报告 | ||
· Test Report | · 测试报告 | 120 | 180 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 10 | 10 |
- | 总计 | 835 | 1075 |
二、信息隐藏、接口设计和松耦合
- 信息隐藏
信息隐藏是指在设计和确定模块时,使得一个模块内包含的特定信息(过程或数据),对于不需要这些信息的其他模块来说,是不可访问的。我们设计的计算模块之对外提供计算结果,并不暴露其中的计算过程和计算过程中产生的数据。
- 接口设计
我们的接口起到的主要是传递信息的功能。即由UI模块将输入的信息通过接口传入计算模块,再由UI模块通过接口读取计算模块计算出的结果进行图形化显示。UI模块只需要知道传递的信息的数据类型,并不需要知道其他任何信息。
- 松耦合
计算逻辑和图形化显示之间并没有任何逻辑联系,两个模块之间只有简单的信息传递。但是信息的数据类型是定死的,也就是说模块必须实现相应类型数据的接收
三、计算模块接口的设计与实现过程
相比于上次的实现,这次的计算模块只增加了一个接口类,用于供前端调用
相比于上次的内容,这次增加了线段和射线,其本质是对直线的左右边界做了限定,所以我们采用扩展原有的Line
类的方法来支持本次对扩展。
本次扩展对Line
类添加了左右边界的属性
- 直线:左右边界都为无穷(使用INT_MAX表示)
- 线段:左右边界都为确定值
- 射线:单边界为确定值,另一边界为无穷
这样,原有的核心计算方法依然可以正常工作,只需在计算出具体Point
交点时对其坐标进行判断即可,即所有线都按直线计算,然后再使用自身的边界对结果进行筛选
此外,这次添加了异常处理功能
这部分是使用c++
自身的异常机制来进行实现的。
我们首先针对题意分析出了一系列可能的异常情况,然后分别在可能出现异常的代码段进行了异常抛出,同时,我们在后端抛出异常时会设定好其中的异常信息,从而使得前端只需捕获异常,然后向用户回显其中的异常信息即可
而对于输入处理方面,我们采用了C++11
的正则表达式来处理,根据题目的要求,我们指定了一系列的正则表达式,对每种图形的输入都做了检查,当检查失败时同样会采用抛出异常的方式来进行处理
四、UML图
五、计算模块接口部分的性能改进
对比上次的实现,这次对浮点数的处理做了一些优化
之前采用的是long double
直接hash
和==
运算,不仅hash
耗时较长,而且会存在精度的问题,为了解决这些方面的问题,这次对浮点数的处理进行了优化
首先根据题目给出的数据范围,我们大致估计出了数据精度只需在float
即可,所以我们首先使用了float
来代替long double
进行hash
操作
虽然这样可以加快hash
操作的速度,但是精度问题依然没有解决,会出现0.999999999
和1.000000000
的问题,所以为了解决这个问题,我们采用了\(\epsilon\)的计算方式。
即对每个浮点数先在高精度上加一个极小值,然后再将其截断为float
,这样像上面提到的问题就解决了。
通过上面一系列处理,不仅hash
计算耗时问题解决了,同时也解决了精度问题,一举两得。
上面是VS性能检测器的分析结果
在上一次个人项目中,count_line_with_line
的主要耗时点在compare
和hash
上,而如今在计算函数内容没有大变动的情况下,get_intersection_with
和compare
与hash
的耗时基本持平,由此可见这个优化带来的收益还是很大的。
六、契约编程
契约式设计就是按照某种规定对一些数据等做出约定,如果超出约定,程序将不再运行,例如要求输入的参数必须满足某种条件。这种严格规定了输入输出方式的编程方式能很好地规范程序接口。从而方便高效地实现覆盖率高的单元测试,从而保证程序的正确性。
而这种编程方式的问题就是需要投入大量的精力和时间去规定契约和实现并验证契约,在时间紧迫的项目中难以大覆盖率实现。
我们在这次作业中契约设计主要应用于模块接口,对传入传出的数据类型有着约定,从而保证模块之间信息传递的准确性。
七、计算模块部分单元测试展示
针对这次的项目新增的一些功能以及异常处理方面,我们也对第一阶段的项目的测试做了很多补充和扩展
上面唯一没有测试的类是main所在的类,因为在UI方面和exe方面进行了相应的测试,也因为和文件的输入输入相关,单元测试起来会比较受环境影响,所以这里并没有进行相关的测试。
/*
两个线段,他们可能只有一个端点相交
*/
TEST_METHOD(TestTwoSegmentIntersecInEnd) {
Line line1("S 0 0 1 1");
Line line2("S 0 0 1 -1");
std::vector<Point> result = line1.get_intersection_with(line2);
Assert::AreEqual(1, (int)result.size());
}
/*
两线段共线,且有一个公共交点(端点)
*/
TEST_METHOD(TestTwoSegmentInSameLineHaveOneIntersection) {
Line line1("S 0 0 1 1");
Line line2("S 1 1 2 2");
std::vector<Point> result = line1.get_intersection_with(line2);
Assert::AreEqual(1, (int)result.size());
}
/*
两线段共线,且无交点
*/
TEST_METHOD(TestTwoSegmentInSameLineHaveNoIntersection) {
Line line1("S 0 0 1 1");
Line line2("S 2 2 3 3");
std::vector<Point> result = line1.get_intersection_with(line2);
Assert::AreEqual(0, (int)result.size());
}
上面是一部分测试代码,可以看到,我们针对一些边缘情况进行了较为全面的测试,涵盖了每一种组合情况,同时,为了便于出错时进行bug修复,我们对比较重要的测试进行了注释解释,从而便于后期维护。
八、计算模块部分异常处理说明
这次的异常处理我们主要考虑了下面几种
- 参数格式不正确(错误字符、多个空格)
- 圆半径r<=0
- 坐标超限
- 输入点重合
- 有无限个交点(图形重合)
针对上面这几种异常情况,我们都会将相应的错误信息包装在exception中抛出,让UI做回显
/*
圆半径异常
*/
TEST_METHOD(IllegalCircleRedix1) {
auto func = [] {
Solution s;
s.add_component("C 0 0 0");
};
Assert::ExpectException<std::exception>(func);
}
/*
非法字符输入(小写字母,特殊符号)
*/
TEST_METHOD(IllegalCharacterInput1) {
auto func = [] {
Solution s;
s.add_component("l 0 0 1 1");
};
Assert::ExpectException<std::exception>(func);
}
/*
输入点重合
*/
TEST_METHOD(PointCollision1) {
auto func = [] {
Solution s;
s.add_component("L 0 0 0 0");
};
Assert::ExpectException<std::exception>(func);
}
/*
两线段共线,且有多个交点(部分重合),期望抛出异常
*/
TEST_METHOD(TestTwoSegmentCoverPart) {
auto func = [] {
Line line1("S 0 0 3 3");
Line line2("S 1 1 5 5");
line1.get_intersection_with(line2);
};
Assert::ExpectException<std::exception>(func);
}
/*
各种坐标超限
*/
TEST_METHOD(ArgumentOutOfBound1) {
auto func = [] {
Solution s;
s.add_component("C 100001 0 1");
};
Assert::ExpectException<std::exception>(func);
}
九、界面模块的详细设计过程
这次UI设计使用的是VS自带的MFC,不得不说MFC实在是有点古老,很多东西又难用又不好用,学习成本非常的高但是性价比比较低。
这次UI设计的大体思路是将读入的数据直接传给计算模块,而绘制函数和交点所需要的数据则从计算模块中读出。
- 从文件导入数据
导入数据相关方法是现在IMPORT按钮的click函数中
void CUIDlg::OnBnClickedImport()
{
// TODO: 在此添加控件通知处理程序代码
m_strHistoryPath = "";//文件选择清空
CFileDialog dlg(TRUE, _T("txt"), NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, _T("文本文件|*.txt||"));
if (dlg.DoModal() == IDOK)
{
m_strHistoryPath = dlg.GetPathName();
if (m_strHistoryPath == "")
{
MessageBox(_T("未选择文件!"));
return;
}
}
else //取消文件导入
{
return;
}
CStdioFile file;
CString szLine;
int i = 0;
file.Open(m_strHistoryPath, CFile::modeRead);
//处理文本第一行
file.ReadString(szLine);
//处理文本输入数据
while (file.ReadString(szLine))
{
std::string str(CW2A(szLine.GetString()));
//向计算模块中传递数据并捕获输入相关的异常
try {
core.add_component(str);
}
catch(std::exception e){
CString cstr;
std::string str;
str = e.what();
cstr = CA2W(str.c_str());
MessageBox(cstr);
}
}
//关闭文件
file.Close();
//更新图形列表
getList(m_list);
}
文件导入时出现的异常除中,选择文件这类文件相关异常由UI模块进行识别,而输入格式错误这类输入异常由计算模块进行识别,UI模块捕获了这些异常后进行处理。UI模块导入的数据会不经处理就传递给计算模块并由计算模块完成处理。
- 手动添加数据
由用户输入方式进行的数据添加在ADD按钮的click函数中实现
void CUIDlg::OnBnClickedAdd()
{
// TODO: 在此添加控件通知处理程序代码
UpdateData(true);
std::string str(CW2A(m_input.GetString()));
//向计算模块中传递数据并捕获输入相关的异常
try {
core.add_component(str);
}
catch (std::exception e) {
CString cstr;
std::string str;
str = e.what();
cstr = CA2W(str.c_str());
MessageBox(cstr);
}
//更新图形列表
m_list.DeleteAllItems();
line_count = 0;
getList(m_list);
}
- 图形列表的维护
在UI模块中,图形列表的作用是显示进行绘制所用的所有图形的信息,并且为删除功能的实现提供可被选中删除的对象。即在列表中点选图形函数后可按下删除按钮实现对图形的删除。
列表的维护由列表的click函数实现对图形的选择,由getList()
函数实现对列表的更新
void CUIDlg::OnNMClickList1(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
// TODO: 在此添加控件通知处理程序代码
*pResult = 0;
NMLISTVIEW* pNMListView = (NMLISTVIEW*)pNMHDR;
if (pNMListView->iItem != -1) // 如果iItem不是-1,就说明有列表项被选择
{
m_selected = m_list.GetItemText(pNMListView->iItem, 0);
m_id = m_list.GetItemText(pNMListView->iItem, 1);
//将选中的图形信息更新到选中的图形文本框中
UpdateData(false);
}
}
void CUIDlg::getList(CListCtrl &m_list)
{
std::vector<Line>::iterator line_iter;
std::vector<Circle>::iterator circle_iter;
line_list = core.get_all_line();
circle_list = core.get_all_circle();
//处理直线类型的数据的信息
for (line_iter = line_list.begin(); line_iter != line_list.end(); line_iter++)
{
std::string str;
if (line_iter->k == INT_MAX)
{
str = "x=" + std::to_string(line_iter->b);
}
else if (line_iter->k == 0)
{
str = "y=" + std::to_string(line_iter->b);
}
else
{
str = "y=" + std::to_string(line_iter->k) + "x+" + std::to_string(line_iter->b);
}
CString id;
id.Format(_T("L%d"), line_iter->id);
m_list.InsertItem(line_count, CA2W(str.c_str()));
m_list.SetItemText(line_count++, 1, id);
}
//处理圆的数据的信息
for (circle_iter = circle_list.begin(); circle_iter != circle_list.end(); circle_iter++)
{
std::string str;
str = "(x-" + std::to_string(circle_iter->center->x) + ")^2 + (y-"
+ std::to_string(circle_iter->center->y) + ")^2 = " + std::to_string(circle_iter->r);
CString id;
id.Format(_T("C%d"), circle_iter->id);
m_list.InsertItem(line_count, CA2W(str.c_str()));
m_list.SetItemText(line_count++, 1, id);
}
}
- 删除功能
删除功能实现在DELETE按钮的click函数中,删除的对象为用户在列表中选中的图形。
void CUIDlg::OnBnClickedDelete()
{
// TODO: 在此添加控件通知处理程序代码
CString t;
CString id;
int id_int;
t = m_id.Left(1);
id = m_id.Right(1);
if (t == _T("L"))
{
id_int = _ttoi(id);
core.delete_line_component(id_int);
}
else if (t == _T("C"))
{
id_int = _ttoi(id);
core.delete_circle_component(id_int);
}
else return;
//更新图形列表
m_list.DeleteAllItems();
line_count = 0;
getList(m_list);
}
- 绘制功能
按下绘制按钮后,UI模块会将图形列表中的所有图形绘制在画板上。绘制功能实现在PAINT按钮的click函数中
void CUIDlg::OnBnClickedPaint()
{
std::vector<Line>::iterator line_iter;
std::vector<Circle>::iterator circle_iter;
//获取画板信息
CRect rect;
CWnd* pWin = GetDlgItem(IDC_DRAW);
pWin->GetClientRect(rect);
CDC* pDc = pWin->GetDC();
pDc->Rectangle(rect);
CBrush myBrush;
CBrush blueBrush;
CPen blackPen;
CPen redPen;
CPen bluePen;
myBrush.CreateSolidBrush(RGB(192, 250, 233));
blueBrush.CreateSolidBrush(RGB(0, 0, 255));
blackPen.CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
redPen.CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
bluePen.CreatePen(PS_SOLID, 1, RGB(0, 0, 255));
//将画板重置
pDc->FillRect(rect, &myBrush);
//绘制坐标轴
pDc->SelectObject(&blackPen);
pDc->MoveTo(rect.left, rect.bottom / 2);
pDc->LineTo(rect.right, rect.bottom / 2);
pDc->MoveTo(rect.right, rect.bottom / 2);
pDc->LineTo(rect.right - 20, rect.bottom / 2 + 10);
pDc->MoveTo(rect.right, rect.bottom / 2);
pDc->LineTo(rect.right - 20, rect.bottom / 2 - 10);
pDc->MoveTo(rect.right / 2, rect.bottom);
pDc->LineTo(rect.right / 2, rect.top);
pDc->MoveTo(rect.right / 2, rect.top);
pDc->LineTo(rect.right / 2 + 10, rect.top + 20);
pDc->MoveTo(rect.right / 2, rect.top);
pDc->LineTo(rect.right / 2 - 10, rect.top + 20);
int R_MAX = rect.right / 2/ ratio;
//将原点设为坐标轴心
pDc->SetViewportOrg(rect.right / 2, rect.bottom / 2);
// TODO: 在此添加控件通知处理程序代码
line_list = core.get_all_line();
//画线类型的图形
pDc->SelectObject(&redPen);
for (line_iter = line_list.begin(); line_iter != line_list.end(); line_iter++)
{
float k = line_iter->k;
float b = line_iter->b;
float left_limit = line_iter->leftLimit < -10000 ? -10000 : line_iter->leftLimit;
float right_limit = line_iter->rightLimit > 10000 ? 10000 : line_iter->rightLimit;
if (k == INT_MAX)
{
if (b > R_MAX)b = 10000;
pDc->MoveTo(b*ratio, -(int)left_limit*ratio);
pDc->LineTo(b*ratio, -(int)right_limit*ratio);
}
else
{
if (right_limit > R_MAX) right_limit = R_MAX;
pDc->MoveTo((int)left_limit*ratio, -(int)calY(left_limit, k, b)*ratio);
pDc->LineTo((int)right_limit*ratio, -(int)calY(right_limit, k, b)*ratio);
}
}
//画圆的图形
for (circle_iter = circle_list.begin(); circle_iter != circle_list.end(); circle_iter++)
{
float x = circle_iter->center->x*ratio;
float y = circle_iter->center->y*ratio;
float r = circle_iter->r*ratio;
CPoint pLeftUp((int)(x - r), (int)(-y - r));
CPoint pRightDown((int)(x + r), (int)(-y + r));
if (pLeftUp.x > R_MAX*ratio) continue;
CRect circle(pLeftUp.x, pLeftUp.y, pRightDown.x, pRightDown.y);
pDc->SelectStockObject(NULL_BRUSH);
pDc->Ellipse(&circle);
}
std::unordered_set<Point> points;
std::unordered_set<Point>::iterator point_iter;
try {
points = core.get_all_intersection(true);
}
catch (std::exception e) {
CString cstr;
std::string str;
str = e.what();
cstr = CA2W(str.c_str());
MessageBox(cstr);
}
//画交点
pDc->SelectObject(&bluePen); //蓝笔标点,点实际是一个实心方块
for (point_iter = points.begin(); point_iter != points.end(); point_iter++)
{
float x = point_iter->x * ratio;
float y = point_iter->y * ratio;
if (x > R_MAX*ratio) continue;
CRect point(x - ratio/4, -y - ratio/4, x + ratio/4, -y + ratio/4);
pDc->FillRect(point, &blueBrush);
}
//在交点个数文本框中更新交点的个数
m_result = points.size();
UpdateData(false);
}
由于MFC并没有提供任何绘制坐标系的简便方法,因此在这个方法一开始会进行坐标轴的绘制(其原理与绘制直线类似)。另一方面,MFC也没有提供射线和直线的绘制方法,因此射线和直线的实现采用规定其左右区间为一个固定值的方法实现(即画一条超出画板的线段)。对于超出画板的图形,方法中检测了其坐标并截去了其超出画板的部分。
对于交点的显示,为了用户能够清晰的看见交点的位置,我们使用一个实心的有一定边长的蓝色正方形来表示。
由于MFC并不支持带小数的坐标的计算(即MFC最小的绘图单位为整型),所以我们采用了将计算模块中给出的坐标进行一定坐标变换,将其在UI模块中等比例放大的方法来降低绘图误差。
十、界面模块与计算模块的对接
class _declspec(dllexport) core {
private:
Solution s;
public:
void add_component(std::string component);
void delete_line_component(int id);
void delete_circle_component(int id);
std::unordered_set<Point> get_all_intersection(bool force);
std::vector<Line> get_all_line();
std::vector<Circle> get_all_circle();
};
- 输入接口
UI模块通过add_component(std::string component)
向计算模块传递用户输入的数据
UI模块通过delete_line_component(int id)
向计算模块传递要被删除的直线类型图形的id
UI模块通过delete_circle_component(int id)
向计算模块传递要被删除的圆类型图形的id
- 输出接口
UI模块通过std::unordered_set<Point> get_all_intersection(bool force)
从计算模块接收交点的信息
UI模块通过std::vector<Line> get_all_line()
从计算模块中接收直线类型图形的信息
UI模块通过std::vector<Circle> get_all_circle()
从计算模块中接收圆类型图形的信息
十一、描述结对的过程
这次结对编程使用了VS的live share和QQ的屏幕共享。live share功能非常强大,能够实现两个人共同对代码进行修改,但是经常出现卡顿,而且受邀者无法自由访问文件,交流上会出现很多不便。
QQ的屏幕共享功能虽然清晰度低但是比较流畅,便于交流。不过功能有限,自然是无法做到共同编辑代码。
十二、结对编程的优缺点
- 结对编程的优点
可以做到一人写代码一人复审,产出代码的正确率高,可靠性强
能够做到能力的互补,结对双方都能发挥自己的优势
能够在结对的过程中学系对方的长处,积累经验
- 结对编程的缺点
当两人风格区别较大时会出现内耗,反而降低效率
有人更喜欢一个人编程,多人一起编程反而会分散注意力
如果两人水平相差较大会出现一个人一直写一个人一直看的情况,1+1<2
- 我的优点
学习能力较好,可以快速上手没接触过的工具
逻辑性强,能够有条理的进行分析
交流能力强,能够很好的适应队友,与队友磨合
- 我的缺点
编程能力较弱,代码风格有点屎
独立解决问题的能力较差,往往不能快速的解决bug
- 搭档的优点
编程能力强,能快速写出优美的代码
重视测试,测试做的十分详尽
善于交流,我们经常能够一起语音讨论一些问题
- 搭档的缺点
细节不够到位
十三、模块松耦合
由于并未提前与对方组(17231162 17373321)商量好接口,所以交换dll后需要在UI模块进行的适应性更改较多。
- 交换模块的更改
class GeometryFactory{
public:
GeometryFactory();
/* Modification */
int addLine(int type, long long x1, long long x2, long long y1, long long y2); // 添加直线,传入四个参数,其中type详见constant.h, 返回值为id,会抛出各种异常
int addCircle(long long x, long long y, long long r); // 添加圆, 返回值为id,会抛出各种异常
void remove(int id); // 删除几何对象,传入参数为id
/* Query */
Line getLine(int id); // 获取已经添加的直线,传入参数为id
Circle getCircle(int id); // 获取已添加的原,传入参数为id
vector<Point> getPoints(); // 获取所有的交点
int getPointsCount(); // 获取交点总数
int addObjectFromFile(string & message); // 解析文件内的一行输入,如“L 0 0 1 1”或“C 0 0 3”,会抛出各种异常
};
这是对方的接口。可以看到,我们两组间的主要区别有:
- 直线的存储方式
我们组采用点斜式的存储方法,即用k和b来表示直线;而对方组采用一般式的方法来存储直线,即使用a、b、c来表示直线。这一差异影响了UI模块在列表中显示图形函数的方法和绘图的方法
- 获取图形数据的方法
我们组可以直接获取全部的图形数据,而对方组需要传入图形id才能传出对应的图形数据,这影响了图形列表的更新。
- 图形数据的保存方法
我们组将圆和直线分为两大类,在每一大类中唯一的id确定唯一的图形。而对方组虽然也将圆和直线分为两大类,但是id并未分类,唯一的id在所有图形中确定唯一的图形
综上,UI模块的更改主要集中在getList()
函数和绘图函数
更改后的getList()
函数:
void CUIDlg::getList(CListCtrl &m_list)
{
std::map<int, string>::iterator id_iter;
std::vector<Line>::iterator line_iter;
std::vector<Circle>::iterator circle_iter;
for (id_iter = id_list.begin(); id_iter != id_list.end(); id_iter++)
{
if (id_iter->second == "L")
{
Line line;
line = core.getLine(id_iter->first);
line_list.push_back(line);
std::string str;
if (line_iter->b == 0)
{
str = std::to_string(line_iter->a) + "x+" + std::to_string(line_iter->c) + " = 0";
}
else if (line_iter->a == 0)
{
str = std::to_string(line_iter->b) + "y+" + std::to_string(line_iter->c) + " = 0";
}
else
{
str = std::to_string(line_iter->a) + "x+" + std::to_string(line_iter->b) + "y+" + std::to_string(line_iter->c) + " = 0";
}
CString id;
id.Format(_T("%d"), id_iter->first);
m_list.InsertItem(line_count, CA2W(str.c_str()));
m_list.SetItemText(line_count++, 1, id);
}
else
{
Circle circle;
CString id;
id.Format(_T("%d"), id_iter->first);
MessageBox(id);
circle = core.getCircle(id_iter->first);
circle_list.push_back(circle);
std::string str;
str = "(x-" + std::to_string(circle_iter->a) + ")^2 + (y-"
+ std::to_string(circle_iter->b) + ")^2 = " + std::to_string(circle_iter->r);
//CString id;
id.Format(_T("%d"), id_iter->first);
m_list.InsertItem(line_count, CA2W(str.c_str()));
m_list.SetItemText(line_count++, 1, id);
}
}
}
由于id不分大类,因此UI部分采用了map<int, string>
的容器来存储id,key为id值,value为id对应的图形所属的大类(圆或直线),以此来通过对方dll的Line getLine(int id)
和Circle getCircle(int id)
来读取图形数据。
绘图函数主要更改的是直线的绘图方法,即将点斜式改为一般式,因此不在此赘述。另外删除和添加的方法也做了略微更改,主要目的是为了适应对方dll的id存储方式。
- 交换模块出现的问题
由于我们组UI模块对于数据的读入是不做任何处理直接扔给计算模块的,所以交换dll后我们的UI模块无法使用对方模块的int addLine(int type, long long x1, long long x2, long long y1, long long y2)
和int addCircle(long long x, long long y, long long r)
接口,只能使用int addObjectFromFile(string & message)
进行输入处理。但是对方的这个接口似乎存在一定问题,无法正确的处理字符串,如下图正确的字符串也会报出格式错误的异常,因此无法正确的实现交点计算和绘制的功能
经测试int addLine(int type, long long x1, long long x2, long long y1, long long y2)
和int addCircle(long long x, long long y, long long r)
接口没有问题,因此我们为UI增加了字符串处理接口来适配这两个接口,以解决这个问题。
增加的字符串输入处理函数如下
void CUIDlg::addStr(std::string str)
{
vector<string> strs = testSplit11(str, " ");
int id;
if (strs.at(0) == "L")
{
id = core.addLine(1, atoi(strs.at(1).c_str()), atoi(strs.at(3).c_str()), atoi(strs.at(2).c_str()), atoi(strs.at(4).c_str()));
id_list.insert(pair<int, string>(id, "L"));
}
else if (strs.at(0) == "R")
{
id = core.addLine(2, atoi(strs.at(1).c_str()), atoi(strs.at(3).c_str()), atoi(strs.at(2).c_str()), atoi(strs.at(4).c_str()));
id_list.insert(pair<int, string>(id, "L"));
}
else if (strs.at(0) == "S")
{
id = core.addLine(3, atoi(strs.at(1).c_str()), atoi(strs.at(3).c_str()), atoi(strs.at(2).c_str()), atoi(strs.at(4).c_str()));
id_list.insert(pair<int, string>(id, "L"));
}
else if (strs.at(0) == "C")
{
id = core.addCircle(atoi(strs.at(1).c_str()), atoi(strs.at(2).c_str()), atoi(strs.at(3).c_str()));
id_list.insert(pair<int, string>(id, "C"));
}
}
vector<string> CUIDlg::testSplit11(const string& in, const string& delim)
{
vector<string> ret;
regex re{delim};
return vector<string>{
sregex_token_iterator(in.begin(), in.end(), re, -1),
sregex_token_iterator()
};
return ret;
}
增加后列表的显示和图形的增删都可正常运行,但是在获取交点时仍会出现问题,经调试后发现是对方的int getPointsCount()
接口无法正确的通过vector容器传输数据。之后在对方修改dll,将接口改为int getPointsCount(double *x, double *y, int count)
,通过传递数组指针获得数据后解决了这个问题。至此,交换dll后UI的基本功能都得以实现