一、项目地址
- 教学班级:006
二、开发耗时评估
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 15 | 10 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 60 | 90 |
· Design Spec | · 生成设计文档 | 0 | 0 |
· Design Review | · 设计复审 (和同事审核设计文档) | 0 | 0 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 0 | 0 |
· Design | · 具体设计 | 90 | 210 |
· Coding | · 具体编码 | 420 | 500 |
· Code Review | · 代码复审 | 120 | 240 |
· Test | · 测试(自我测试,修改代码,提交修改) | 90 | 210 |
Reporting | 报告 | ||
· Test Report | · 测试报告 | 0 | 0 |
· Size Measurement | · 计算工作量 | 5 | 5 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 0 | 0 |
合计 |
三、接口设计
这次的项目涉及到了前后端两个部分,为了实现前后端的分离,我们首先将前后端的职责进行了明确的划分。
- 前端:仅负责做数据输入、获取输出、根据获取的输出进行图形绘制
- 后端:负责接收前端传入的输入、对输入进行合法性检查、提供核心计算功能以及数据获取的接口
由此,这个项目的前后端就清晰的分离开了,具体表现在
- 前端不依赖具体后端:即使后端更换了实现,只要提供相应的数据和计算接口,前端不需要做任何更改
- 后端不需要了解前端:后端只需提供自身的数据以及计算功能,不需要知道前端如何使用自身的数据,即使脱离前端,后端的计算功能依旧可以独自使用
然后,根据最终产品的功能需求,我们在后端模块中设计了如下几个核心接口
- 添加数据的接口,前端只需将原始输入传入即可
- 计算功能接口,前端通过调用可以让后端开始计算
- 数据获取接口,前端通过调用来获取后端内部存储的数据
- 删除接口,前端通过调用让后端对相应的数据进行删除
四、计算模块接口的设计和实现
相比于上次的实现,这次的计算模块只增加了一个接口类,用于供前端调用
相比于上次的内容,这次增加了线段和射线,其本质是对直线的左右边界做了限定,所以我们采用扩展原有的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
的耗时基本持平,由此可见这个优化带来的收益还是很大的。
下面是优化前的计算消耗分布
七、Design by Contract与Code Contract
契约式编程在多人开发中使用较为广泛,其主要特点就是开发建立在规约之上,所有人共同制定统一的代码规约,其一般包括
- 输入参数的规约
- 输出结果的规约
- 不变性的规约
基于团队定制的一系列规约,每个人都只需要注意自身是否完成了规约中规定的内容即可,而不需额外的顾虑。
这种方式的好处是显而易见的,每个人根据自身开发范围相关的规约进行开发,而无需考虑额外的事情,由此肯定会带来代码耦合度的降低以及团队开发效率的提升。
但同时这个方式也存在一些弊端,首先,规范的制定是一件很难的事情,规范强调其稳定性,如果规范频繁发生变动,那么所有相关的开发人员都要受到影响。其次,为了是自身完成的代码满足规约,一般会采用一种防御式编程的方式进行开发,这种方式的特点为广用Assert断言,会使得代码变得比较冗杂。
而在我们这次结对开发中,我们在前后端接口上使用了这种方式,我们先根据需要完成的功能共同探讨了后端需要给前端提供的接口,制定了接口的输入输出以及异常的规范,然后再进行开发,前后端可以同时进行,效率很高。
八、计算模块单元测试
针对这次的项目新增的一些功能以及异常处理方面,我们也对第一阶段的项目的测试做了很多补充和扩展
上面唯一没有测试的类是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,这个功能很强,但是因为网络的问题,一般40-60分钟持续连接后就会出现卡顿,优点是可以同时开发以及编码。
而QQ屏幕共享的优点是基本没有卡顿,但是缺点是画质较差且不能同时编辑,指出具体某行代码时也有些麻烦
十三、结对编程的优缺点
结对编程的优点:
- 结对编程可以两人写一份代码,一份代码经过两人的审查,可靠性更高,不容易出问题
- 结对编程的两人可以能力互补,相互弥补彼此能力的不足
- 结对编程可以起到教育作用,有经验者可以在开发的过程中对新手进行教学
- 结对编程中两人可以交流和共同设计,有利于设计出更优的实现
结对编程的缺点:
- 若两人水平相差较多,可能熟练者会花费相对较多的时间在教学中,效率不如单人开发
- 若结对两人的关系不和,也可能存在配合不默契或者无法合作的情况
- 有的人可能不喜欢自己编程时有他人观看,这时可能会对开发者造成心理上的不适
我的优点:
- 编码速度较快,能够较快将设计转化为实现
- 重视测试,一般是先写接口再写测试最后写实现
- 设计能力较好,能够较清晰的划分系统各个模块的关系和依赖,降低系统的耦合度
我的缺点:
- 细节问题思考不够到位,常出现细节问题没有考虑到
- 核心算法的设计能力较弱
搭档的优点:
- 善于聆听,特别表现在能够聆听我的想法然后提出自己的意见,乐于接受别人的建议
- 善于学习,能够较快学习和使用工作中所需要的技术和工具
- 靠谱,所谓靠谱,就是事事有回应,每次我去找他沟通他基本都是秒回应,从不敷衍咕咕咕
搭档的缺点:
- 信心不足,有时面对一些挑战表现的有些怀疑和犹豫,但最终其实都可以战胜,所以缺乏自信
十四、模块松耦合
我们组采用的是17231122 李沛熙和17373321 游子诺小组的dll进行的松耦合实验
由于并未提前与对方组商量好接口,所以交换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的基本功能都得以实现