2016年11月24日 星期四

開發的函式物件導向化2(CreateThread)

如之前講的,字太多很累,而且這裡是草稿,所以我就直接上傳Word和範例了

當然是我自己做的,所以版權所有,請勿亂傳
主要是講如何製作Thread,並將它物件導向化

連結如下
內容:
https://drive.google.com/open?id=0B6u5oifJv3EKdGZiY0YyZlQwQkk
範例:
https://drive.google.com/open?id=0B6u5oifJv3EKejhScHBmM1d4bE0

寫程式的好習慣

總之就是多加註解了,其實上班以後,如果您有做案子的話,通常都一個案子好幾萬行,想想各個地方都是不同的想法,要是忙起來又接兩三個案子,來回不同的切換,就算您是功力很深厚的,怎麼可能都記的住,

也就是所謂的註解可能記不住,不註解更不可能記住

其實註解也是一門學問的,學問的意思不是說您要註解的很深奧或很文言,他其實傾向直接明白,您當時想什麼,有什麼特別構想或者是為什麼要那樣設計,就直白的寫上去,通常在註解時不要太多專業名詞,除非您是給做很久的人看的,他對專業名詞有基本認識,
想想如果您是初學者,我設計一個模組是用來搭架構的,例如底層建置好,您只要繼承我這個class的某個virtual function,他就會自動的在執行緒執行,

想想我這段話,要是看的人不知道執行緒是什麼,那是不是就卡住了?

就算您知道執行緒是什麼,我用一個專業名詞virtual function,沒學過AOOP的是不是又卡住了?
跟您說他會自動執行,我沒給範例,您怎麼知道真的他會在執行緒自動執行?
要是我再把這個class包成lib並把.cpp藏起來,您真的知道怎麼用嗎?
想想每碰到一個地方都是一個關卡,要是我註釋再改成英文,英文不好的不就又死了?

又或者是其實在公司上班,假設這是一個個人的東西,結果我懶或者是為了保密,根本沒加註釋,那是不是每個人都要來問我才會使用呢?
所以您看完這段會了解到,其實如果您想要快速的成長或者是有一群相同向前的同伴(要確保大家都很努力拉,當然也是要賺到錢),並且以最快速度上手並完成案子,

其實到最後很多東西是解密的,就是lib附.cpp,並提供看得懂的註釋,有時間要提供該模組的範例程式,並且有實際應用的案子可以給人參考,甚至,人家來問時,通常有時候我們會做一些拖拖拉拉的回應,為了拖人家時間或者是讓人家知難而退,故意不教人家,這也是不行的,要求效率要以最直白的方式,讓人家上手

這些就是所謂的好習慣

寫Code是一件很累的是,一個經典的function您要靠自己想出來,可能要一兩年一直修改,還是都有可能失敗做不完整,

想想一個大型案子(除非您做小的案子,但是往往小的案子不賺錢),通常要數10個經典算法或者是好的設計才做得出來,

您卡別人人家再卡您,案子是做不好的,但其實如果您是該環境的技術上游0.0有時候還是要保護自己,避免東西被人家挖走
總之,我們還是維持人性本善,通常東西只要用出來,您最好不要有再藏起來的想法,因為會發生我前面描述的那些,導致大家效率不好,團隊不賺錢,您怎麼拿到比較多的錢呢?

這是基本想法,當然一些技術領先還是要藏起來啦@@,不然就您的案子有用,其他案子都不要開發相關題目,這樣也是可以,但是往往不同案子會有相同的需求,其實藏不住的,

而且其實一般趕案子,一些模組不可能完整或者是沒有Bug,保密到最後只會讓大家陷入負向循環(除非您已經不想向前衝了,不然負向循環是一件很可怕的事)

例如您藏程式碼,人家用到來問您,結果您花額外時間回答很多人相同問題,結果您也有案子壓力,一急做不好,做不好東西有問題,人家又找您協助,你會發現一直窮忙,很糟糕的

所以第一件事,仔細評估要拿多少東西出來,可以讓環境有正向成長,通常講這樣就是全部拿出來拉,不想拿不如就不要使用,不要找其他人跟您一起偕同做事
這算是一個好習慣

再者我們寫東西時要把可以reuse的東西模組化,讓他可以重複使用,也就是您花一年的努力的成果,下一個人使用可能花5分鐘就可以達到相同效果,這樣團隊獲利才會比較快速

另外我們設計東西時,要規劃後面可能要做的事情…幹,有蟑螂,租到蟑螂屋…哭哭

總之就是設計的任何東西要有預先保留擴展的可能,例如您拉一個參數窗口,總不能拉的小小的,並且您的頁面剛剛好擺滿,要使用的參數,通常會建置一些分頁(例如PageControl),將參數分群在不同頁面,讓往後您需要新增參數時,可以有地方放,這也是要注意的

不只這個,例如您新增光源模組,您要考慮未來是否會有多組光源要控制,如果會有的話,記得模組化您的光源程式控制片段,並且使用一些可以調整組數的結構,例如使用vector,這樣往後要多幾組有可花比較小的力氣做新增,

您沒做上面這些準備當然沒關係,但是往往您下次要修改時,您可能要花很多倍的時間,寫這個功能,其實多做一點設計和準備是好事,儘管您目前交件時沒用到

註解,模組化,不要太摳,預留備載以利往後修改,總之最重要的就是,做事直接明白一點,不夠直白,喜歡拐彎抹角,往往您寫出來的東西,也會拐彎抹角,Code人家不好接手,您藏一個提示,對其他接手維護的人會增加困擾,少一個重要註解,人家要多花一段時間看您在寫什麼,想想要是您同伴也這麼做,陷入負向循環是遲早的事…不要不信邪喔
基本上多想兩分鐘,做一些比較完善的構思,再開始執行,會比您塗塗改改要容易多了,而且其實剛開始會看起來做事速度比較慢,您東西做大後您會發現人家會操死,而您有事前規劃會比較好做事(除非您只是要接小案子維生爾以,東西改完下個案子又是打掉重練)

四種型態轉換

http://windsplife.blogspot.tw/2010/09/cstaticcast-dynamiccast-reinterpretcast.html
http://ot-note.logdown.com/posts/173174/note-cpp-named-type-convertion

有四種static_cast,dynamic_cast,reinterpret_cast,const_cast

其實網頁上寫的應該都是很詳細了
所以這裡我只是就我認知寫一下,

Static_cast<T>exp是靜態轉換
它等同於(T)exp

直接舉例好了
int a = 5;
double b = (int)a; or
double b = static_cast<int>(a);


總之主要就是算術轉型

一般用這個大部分都是用來做算術轉型

雖然他也可以用來轉型指標,但是不保證安全,尤其在多重繼承的情況下,幾乎是很容易出錯,也就是失敗

而主要指標一般有需要用來轉型,一般是在一般的變數結構裡

例如一個一維陣列
char* rrr = new char[100 * 3];
就是一個一維陣列存RGB存100筆
一般我們可以宣告一個結構去接他

Struct aaa
{
       Char r;
       Char g;
       Char b;
};

Aaa* temp = (aaa*)rrr;


這樣只要temp[xx].r,就可以很明瞭的作存取值的動作

一般做繼承的class的指標轉換會用dynamic_cast去轉,但是網頁上有說dynamic_cast轉class,class內必須有virtual function才會成功

總之要是有多層結構,記得用dynamic_cast去轉換,轉失敗他會回傳NULL,

雖然說dynamic_cast需要做一些檢查,但是其實在繼承的架構底下,指標轉來轉去,沒用這個基本上會莫名其妙的錯誤,所以有時候還是要乖乖地做轉換


剩餘兩個其實比較不常用
reinterpret_cast就是強制轉換,反正轉換完他就用新的類型的bit結構去處理

例如:

int* i;
char* str = "test";
i = reinterpret_cast<int*>(str);

您會發現*i讀出會是134514704


總之就是強制bit轉換,轉換後直接把原來的bit樣子用轉換後的類型去解讀

至於const_cast其實我也不常用,總之網頁上是將const類型轉換為一般類型,通常會用const都是為了做保護,把他轉換為可讀寫的,一般容易亂,建議少用

virtual和override和final

http://viml.nchc.org.tw/blog/paper_info.php?CLASS_ID=1&SUB_ID=1&PAPER_ID=547

如網頁上寫的,其實很清楚了

我就講一下virtual的注意地方,其實就是雖然virtual可以寫成虛函式,但是其實有時候型態轉換錯誤,或是不小心存取到虛函式時,還是會出錯,所以我為了避免跳出錯誤訊息,其實就算是用來被繼承而已的虛函式,其實我也會把他實例化(在cpp),也就是寫個他的實體函式,這樣就算錯誤時他也只會執行到空函式,提供給您們參考

總之virtual很好用的,
class CA
{
public:
       virtual void func1(int a)
       {
               ////
               // 其他程式片段
               ////
       }
       virtual void func2(int a)
       {
               ////
               // 其他程式片段
               ////
        }
};

class CB : public CA
{
public:
       void func1(int a) {
               CA::func1(a);
               CA::func2(a);
               ////
               // 其他程式片段
               ////
       }
};

這裡這樣寫的是,如果您繼承後,執行CB的Func1,其實CA的Func1還是存在的如您有寫處理寫在CA的Func1,您可以使用CA::func1(a);順便執行父親class的函式,這是一個小技巧

其實在寫很多繼承架構時,會用到這個,當然其他父class函式也是可以存取的,總之就加CA::其他父class函式,只要您滿足繼承關係(就public,private,protected),要怎樣使用就怎樣使用

例外就簡單講override和final吧,其實連結裡面已經有寫很清楚了,

Override加在virtual函式後面,他會檢查是否有蓋到繼承的class的函式,沒蓋到編譯會出錯,

Fianl則是該函式或是class是最後端點,不可以做繼承用,如果您繼承了,他編譯會出錯

基本上在classic Borland compiler是沒有這個的,要用新的clang c++11 compiler可以編譯過,由於後來我在處理DLL溝通時,在classic Borland compiler(32bit)和clang c++11 compiler(64bit)切換實在麻煩,就是語法上有些差異,要額外處理,因此我都改成clang c++11 compiler(32bit/64bit)了

至於CodeGuard基本上已經棄用了,我在等C++ Builder支援AddressSanitizer再來做偵錯(不確定是否會支援),基本上只要少直接用new指標傳來傳去,基本上不太會會出現沒有delete到的狀況,而且說實在的,其實如果動態產生指標傳來傳去,其實CodeGuard好像也抓不到,所以基本上CodeGuard對我個人用處不是很大(重點CodeGuard好像沒維護了,其實案子一大,有時候跑一跑會程式會出錯,總之感覺用處不大)

而最近其實有測試了Intel Inspector,他在XE10.1 berlin的64bit環境是可以抓記憶體錯誤的(32bit不行),有空可以試試看