什麼是野指針?
一個母親有兩個小孩(兩個指針),一個在廚房,一個在臥室,(屬於不同的代碼塊,其生存期不同)母親讓在廚房的小孩帶一塊蛋糕(指針指向的對象)給在臥室的小孩,這樣在臥室的孩子才肯寫作業。但這個在廚房的小孩比較淘氣,他在走出廚房時自己將蛋糕吃了,沒能帶出來。而在臥室的沒有吃到蛋糕,所以不肯完成他的作業。結果母親卻不知道臥室的孩子沒有吃到蛋糕,還以為作業完了。結果第二天她就被老師召喚到辦公室了。事情麻煩了。
這樣,那個在臥室的孩子就是野指針了,因為他沒有得到應得的蛋糕,不能完成母親交給他的作業。
這就是c中所講的野指針。上面的小劇本不過演示了一種最基本的野指針的形成過程。更容易出現的情形是coder在編碼時,大意之下使用了已經free過的指針。
對於年輕點的經驗欠缺的coder來說是比較容易犯的錯誤,經驗老到的程序員或者慎重采取成對編程的形式避免這種失誤,或者使用引用計數器防止形成野指針。
總之,在c中,野指針也許性子野,但是控制起來也是有章可循。然而事情在c++中出現了變化。
coder們面臨更大的麻煩了。c++程序員無可避免的要寫很多這樣那樣的類。誰讓c++是面向對象的呢?
我們在寫類的時候難免要用new給類的數據成員分配內存。這本來沒什麼,動態分配內存是一種很常見的基本操作,我們在學數據結構時經常這麼做,不是麼?
但是伙計,事情並非這麼簡單。類是一種高級的用戶自定義數據類型,看起來和結構、枚舉這樣的用戶自定義類型沒啥太大差別。如果你這樣認為....?那你會死的很慘。類太復雜了,普通情況下使用類的對象並沒有太大的問題,但是,當你要復制一個對象時,問題就來了。
比如我們知道,你要用一個對象初始化另一個對象時,c++是按位進行拷貝的,即在目標對象裡創建了初始化對象的一個完全相同的拷貝。這在多數情況下已經足夠了。但是,當你的類在創建時為每個對象分配內存,也就是說類中有new操作。當你的對象創建好後,類也為對象分配了一塊內存。如果你用這個對象去初始化另一個對象時,被初始化的對象和初始化的對象完全一樣。這意味著,他們使用同一塊內存,而不是重新為被初始化的對象分配內存。
這樣麻煩就大了。如果一個對象銷毀了,那麼分配的內存也就銷毀了(別忘了,類是有析構函數的,它負責在對象銷毀時,釋放動態分配的內存。難道你說你不在類中寫上析構部分?那麼可憐的孩子,那你就走向了另一個深淵,當你的程序運行數小時之後,系統會告訴你,內存不夠用了。想象一下把你的程序用在騰訊的服務器上),另一個對象就殘缺不全了,這就像一對連體嬰兒,他們共用了一部分器官,心髒或者肝髒。要救活一個,就犧牲了另一個。一個得病了,另一個也要遭殃。
可以說,這就是c++中更加變態的野指針。
什麼?你說我不用對象初始化對象?那麼我們會不會將一個對象作為變元傳遞給函數呢?我們很多時候都這樣做。有時我們不得不將對象按值傳遞給一個函數,但是你要知道,按值傳遞是什麼意思?它的意思就是,把實參的一個拷貝傳遞給函數。這和剛才的初始化沒什麼兩樣,按位拷貝,函數體內的對象與外面的對象共用一塊內存,即便在函數中的對象沒有對這塊內存進行過操作,但是當函數結束時。。。。析構函數將會被調用......
還有一種與之相反的情況......, 當你想要把一個在函數內的對象值返回給外面的對象時,這時候,會自動產生一個臨時對象,由它容納函數的返回值,並在函數結束時把結果傳給目標。那麼這個臨時對象迅速的被創建,並被迅速的釋放。。。一塊內存被釋放了兩次。其後果是不可預見的。
當你把一個對象的值賦給另一個對象時,如果你沒有重載賦值運算符,那麼也會導致按位拷貝。最終產生一個野指針(一個隱藏在類內的毒瘤),或者釋放同一塊內存多次。
看到了麼?害怕了麼?是不是感到C++到處都是陷阱呢?不但有陷阱,到處都是危險品。所有c中的疑難問題,到了c++就成了一般問題了。好了不廢話了,我們繼續講講解決之道。
對於最後的這種賦值的情況,我們只有通過重載賦值運算符才能解決,也就是避免按位拷貝。
至於前面的都屬於初始化,概括下來就是三種情況:
1.當一個對象初始化另一個對象時,例如在聲明中;
2.把所創建的對象拷貝(按值)傳遞給一個函數時;
3.生成臨時對象時,最常見的就是函數的返回值。
解決初始化時的按位拷貝問題,我們通過創建拷貝構造函數來解決。
基本的拷貝構造函數形式為:
classname (const classname &o)
{
//body here
}
拷貝構造函數就是針對這個問題而設計的。
恩,大家都明白了吧?不要讓你的對象都變成可憐的連體人啊~~~~