2009年4月14日 星期二

C++的內隱型別轉換

這兩天複習C++,又碰到了很詭異的語法

後來發現C++的型別轉換真是萬能


class T{
T();
T(int);
T(T&);
}

我有一個class T而我執行

T t;
t=100;

竟然可以通過編譯,原來在此Compiler很聰明的,會讓
t=100;
變成
t=new T(100);
在此會呼叫Convert Construct(轉換建構子)
此時建構子扮演的型別轉換的角色,C++會自己去找有沒有實作相關的轉換建構子
如果沒有就會編譯不過

像是

class G;
class T{
T(G&)
}

void print(T&){}

main(){
print(new G());
}

他可以通過編譯,因為在這種情況下,參數傳遞用的是initization而非assign
他會自動去尋找有沒有相關的轉換建構子

2009年4月13日 星期一

設計模式-(序) 是設計更是藝術

序言


最近在研讀設計模式(Design Pattern),至從看了有關這類的書
再回頭看看我以前寫的程式,只能用不堪回首來形容
我覺得設計模式是每個物件導向程式設計師都該看的
在眾多設計模式的書籍當中,個人推薦這本:大話設計模式 出版(代裡)社:悅知


這本是之前去紀伊國屋看到的,由於紀伊國屋的東西都貴到昏倒
所以買這本的時候我很猶豫,加上我對悅知出品的印象都是:夠廣不夠深
就是悅知有關資訊的書籍都是探討範圍很廣,但是不夠深入(我是讀書狂,所以對出版社風格會有刻板映像)
但是這本推翻了我以往的想法,這本大話設計模式用對話跟日常生活例子為對象來延伸到設計模式,除了探討內容夠之外,容易閱讀是特點,這本大話設計模式我花了數個小時就閱讀完畢,而且也吸收豐富,相較於之前培生出版社那本,這本真是好物。

回到正題,就算不懂設計模式,程式寫多了也多多少少會用到類似的概念
Java或是.NET等都用到不少相關的設計模式,有拜讀過那些core的原始碼的話,一開始會像看天書一樣,有看沒有懂,但是了解其中的設計模式後,會發覺他是那麼的美麗。

設計模式:是設計更是藝術



一個設計好的程式,就像是美麗的女人般讓人百看不厭,以前從未注意過程式可以這樣散發如寶石般的光輝,想想之前我class隨便亂用,幾乎一個函式幾百行那種程式,真的只能用垃圾來形容。
程式寫多了,之後就只是語法上的問題,我甚至斷言照著書作人人都可以寫出一個系統。
尤記得我大學時候一位同學,他們的專題是作進銷存系統,雖然他們都不太會寫程式,但是靠著微軟VS2005的強大到也讓他們做出有模有樣的東西,但是一旦放上戰場,缺點立刻出來,根本就不堪用。
再舉一個比較近的例子,微軟最近在徵招實習生(未來生涯體驗),他們的主題網頁外觀華麗,但是根本經不起考驗,我因為也想報名,所以有去看他們的網頁,一開始我還讚嘆外觀設計的真漂亮,一旦報名按下去,缺點立刻浮現,也就是他們的網頁firefox不支援某些動作,逼得我一定要用IE去跑這是敗點一:瀏覽器的相容性沒做好。
再來就是註冊時明明沒叫我填身分證,登入時卻要填身分證(啥鬼),然後我身份證填好後不給我登入,造成我原本二月多要報名托到三月中,而且我可以登入還是因為我發現身分證少填就可以登入,敗點二:程式邏輯沒設計好,驗證也做不完全。

其他零零總總的缺點,所以說程式人人會寫,有些套裝軟體甚至拉一拉就寫好一套系統(VS之類的),但是是不是經得起考驗的系統才是我們資訊人員的優勢所在,不然套件拉一拉程式就寫好了,工程師還有飯吃嗎?

記得當初上一堂課,助教要抓有沒有抄襲,他就把程式碼瀏覽過一遍之後,就抓出了誰抄誰,他說了一句話:"程式是個藝術,每個人風格皆不同,只要看了程式碼架構,大概就能猜出是誰寫的"
這句話在我碰觸過設計模式之後更是非常認同,程式如果沒設計好,寫出來的只是把螺絲起子,轉過螺絲後就毫無價值,但是設計好的程式就像把萬用刀,什麼事情都可以用,栓了螺絲還能用他做其他事情。

但是設計模式會造成程式效能的降低(function call增加),如何在這之間做取捨,就是程式設計師的功力了。
用我喜歡的RPG來舉兩個極端的例子,一支效能到極限但是沒有設計過的程式就像是萬靈藥,可以解除所有異常狀態(問題),但是用過一次就消失不能再用了。
而一個設計到毫無破綻,但是效能普普的程式,就像是僧侶,可以作到萬靈藥的作用但是一次只能做一件(解毒、治療、增加力量etc),而且還要考慮僧侶有沒有MP(現在的問題適不適合),但是僧侶沒有使用上的限制,只要有MP(設計模組正好能解決問題),就能一再使用。

說了那麼多,主要是在說程式是否能夠Reuse,一隻好的程式除了效能好之外,他還必須能夠重複使用來增加他的生命週期,如此一來才能說他是個有設計的程式。

copy不等於reuse


很多人都學過物件導向程式目標就是寫出內聚力強、耦合力弱的程式,但是會真正去實踐他的人卻不多,我認識許多人都有錯誤的概念,認為寫程式寫到後面就是copy&paste,這種是最要不得的概念,就因為程式沒設計好才會用複製貼上取代程式碼的覆用,當copy&paste用多了的時候,哪天要改就會花費非常大的成本,假如這隻程式用在一百個地方,就要改一百次(老闆會寧用fire這種人請另一個高明的來重構),這時候其實可以考慮用提煉函式的方法解決,但這是重構(Refactor)的部分了,在此不贅言。



接下來我打算花幾個篇幅連講解設計模式的基本23個pattern,希望我能完成

2009年4月5日 星期日

C/C++的Const(下)

const在類別的使用也是非常....有趣


class T{
private:
int data;
public:
void op()const{}

};

java跟C#碰多了,再回來看c++的一些用法真的會覺得很詭異

在此op稱作const member function(常態成員函數)

在跟compiler說函式op不會修改到成員變數(data)

所以如果在op裡面作出修改成員變數動作將會編譯不過

void op()const{data=0;}

同樣的,如果試圖呼叫函式或是回傳 讓compiler覺得你可能修改到成員變數,也會編譯不過

void op()const{dp(data);gp();}\\X
void dp(int data){}
void gp(){}

compiler會怕妳那個函數可能修改到成員變數
修改方式

void op()const{dp(data);}
void dp(const int& data)const{} \\告訴compiler你不會修改到data
void gp()const{}


或者是
int* op()const{return &data;}
也是沒辦法的,因為傳同一個記憶體變數會讓compiler覺得你會修改到他
必須修改成回傳常數的方式
const int* op()const{return &data;}

而當你變數宣告成常數時,如果使用不為const member function也會compiler失敗

class T{
....
void dp();
}
void get(T& t){}
main(){
const T tester;
tester.dp();\\ X
get(tester);\\X
}

必須修改成

class T{
....
void dp()const; \\我不會更改成員
}
void get(const T& t){}\\我不會更改成員
main(){
const T tester;
tester.dp();
get(tester);
}

簡單來說,就是const函數,只能存取同為const的行為
成員函數調用成員函數

呼叫 在呼叫 O/X
const const O
const non-const X
non-const const O
non-const non-const O
或者是將成員宣告成
mutable int data;
mutable是c++的關鍵字,基本上是不建議這樣做,只會讓debug更複雜
或是將迴船方法改成
return const_cast(data)
將const屬性去除


a. const對象只能訪問const成員函數,而非const對象可以訪問任意的成員函數,包括const成員函數.
b. const對象的成員是不可修改的,然而const對象通過指針維護的對象卻是可以修改的.
c. const成員函數不可以修改對象的數據,不管對象是否具有const性質.它在編譯時,以是否修改成員數據為依據,進行檢查.
e. 然而加上mutable修飾符的數據成員,對於任何情況下通過任何手段都可修改,自然此時的const成員函數是可以修改它的

引用自http://hi.baidu.com/maolang0/blog/item/e0546a8bd03a4c14c8fc7a12.html

最後來提個有趣的例子 關於運算子多載跟const

class T { // "unlimited precision int"
  public:
   T& operator++(); // prefix
   const T operator++(int); // postfix
};

為什麼在此要用const呢

是為了user做蠢事像是

T a,b,c;
(a+b)=c;
\\or 防止
(a+b)+c;


以上 最近複習C++ const的小結

C/C++的Const(中)

const在函數裡可以做到保護的作用

void add(Card c);

像這樣一段函式,直接用call-by-value的方式傳遞

如果Card是個非常大的結構,那overhead將會非常的高,而且高到不行(里昂調)

因為他是用copy的方式整個copy一份到stack裡面使用

假如你的c大小是1MB 那麼光作這1MB的copy(包括init跟記憶體規畫)跟傳遞就耗費無數時間

所以通常都會選擇只傳他的指標

void add(Card& c);

這樣子只需要傳遞Pointer(4 byte),而不須整份copy(n byte 過去)

但是有時候我希望傳進去的東西不要被修改,我希望他唯讀

這時候const就小兵立大功了

void add(const Card& c);

改成這樣,就會告訴compiler 傳進來的參數是不可更改,如果做了任何指派的動作就會編譯不過

void add(const Card& c){c=new Card;}\\X

包括把他傳進另一個函式或者是回傳


void add(const Card& c){test(c);}
void test(Card& c){}


此時儘管test沒做什麼,仍然會編譯不過 因為compiler不確定test是否會修改到c

但是改成如下就可以


void test(const Card& c){}\\告訴compiler test不會修改c
\\or
void test(Card c){}\\不共用記憶體 但是失去意義


同樣的,你也不可能回傳c的記憶體位置
Card* add(const Card& c){return &c;}\\X
因為要避免你在函式外面修改他

Card* a=add(c);
a.x=XXX;

但是可以用回傳值使用const的方式解決

const Card* add(const Card& c){return &c;}
const Card* a=add(c);

其中const Card* 代表回傳的是一個常數,a必須也是個常數才能接收


Note:

const Card add();

const用於非指標回傳一點意義都沒有