項目 | 內容 |
---|---|
課程連接 | 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);