這篇文章主要介紹了簡要解讀C++的動態和靜態關聯以及虛析構函數,析構函數在C++編程中平時并不是太常用,需要的朋友可以參考下
C++靜態關聯與動態關聯、C++是怎樣實現多態性的
在現實生活中,多態性的例子是很多的。我們分析一下人是怎樣處理多 態性的。例如,新生被錄取人大學,在人學報到時,先有一名工作人員審查材料,他的職責是甄別資格,然后根據錄取通知書上注明的錄取的系和專業,將材料轉到有關的系和專業,辦理具體的注冊人學手續,也可以看作調用不同部門的處理程序辦理入學手續。在學 生眼里,這名工作人員是總的人口,所有新生辦入學手續都要經過他。學生拿的是統一的錄取通知書,但實際上分屬不同的系,要進行不同的注冊手續,這就是多態。那么,這名工 作人員怎么處理多態呢?憑什么把它分發到哪個系呢?就是根據錄取通知書上的一個信 息(你被錄取入本校某某專業)??梢姡獏^分就必須要有相關的信息,否則是無法判別的。
同樣,編譯系統要根據已有的信息,對同名函數的調用作出判斷。例如函數的重載, 系統是根據參數的個數和類型的不同去找與之匹配的函數的。對于調用同一類族中的虛函數,應當在調用時用一定的方式告訴編譯系統,你要調用的是哪個類對象中的函數。例如可以直接提供對象名,如studl.display()或grad1.display()。這樣編譯系統在對程序進行編譯時,即能確定調用的是哪個類對象中的函數。
確定調用的具體對象的過程稱為關聯(binding)。binding原意是捆綁或連接,即把兩樣東西捆綁(或連接)在一起。在這里是指把一個函數名與一個類對象捆綁在一起,建立關聯。一般地說,關聯指把一個標識符和一個存儲地址聯系起來。在計算機字典中可以査到,所謂關聯,是指計算機程序中不同的部分互相連接的過程。有些書中把binding譯為聯編、編聯、束定、或兼顧音和意,稱之為綁定。作者認為:從意思上說,關聯比較確切, 也好理解。但是有些教程中用了聯編這個術語。 大家在看到這個名詞時,應當知道指的就是本節介紹的關聯。
順便說一句題外話,計算機領域中大部分術語是從外文翻譯過來的,有許多譯名是譯得比較好的,能見名知意的。但也有一些則令人費解,甚至不大確切。例如在某些介紹計算機語言的書籍中,把project譯為“工程”,使人難以理解,其實譯為“項目”比較確切。 有些介紹計算機應用的書中充斥大量的術語,初聽起來好像很唬人、很難懂,許多學習 C++的人往往被大量的專門術語嚇住了,又難以理解其真正含義,不少人“見難而退”。 這個問題成為許多人學習C++的攔路虎。因此,應當提倡用通俗易懂的方法去闡明復雜的概念。其實,有許多看起來深奧難懂的概念和術語,捅破窗戶紙后是很簡單的。建議讀者在初學時千萬不要糾纏于名詞術語的字面解釋上,而要掌握其精神實質和應用方法。
說明:與其他編程語言相比,例如Java、C#等,C++的語法是最豐富最靈活的,同樣也是最難掌握的,大家要循序漸進,莫求速成,在編程實踐中不斷翻閱和記憶。
前面所提到的函數重載和通過對象名調用的虛函數,在編譯時即可確定其調用的虛函數屬于哪一個類,其過程稱為靜態關聯(static binding),由于是在運行前進行關聯的, 故又稱為早期關聯(early binding)。函數重載屬靜態關聯。
在調用虛函數時并沒有指定對象名,那么系統是怎樣確定關聯的呢?讀者可以看到,是通過基類指針與虛函數的結合來實現多態性的。先定義了一個指向基類的指針變量,并使它指向相應的類對象,然后通過這個基類指針去調用虛函數(例如“pt->display()”)。顯然,對這樣的調用方式,編譯系統在編譯該行時是無法確定調用哪一個類對象的虛函數的。因為編譯只作靜態的語法檢査,光從語句形式(例如“pt->display();”)是無法確定調用對象的。
在這樣的情況下,編譯系統把它放到運行階段處理,在運行階段確定關聯關系。在運行階段,基類指針變量先指向了某一個類對象,然后通過此指針變量調用該對象中的函數。此時調用哪一個對象的函數無疑是確定的。例如,先使pt指向grad1,再執行“pt->display()”,當然是調用grad1中的display函數。由于是在運行階段把虛函數和類對象“綁定”在一起的,因此,此過程稱為動態關聯(dynamic binding)。這種多態性是動態的多態性,即運行階段的多態性。
在運行階段,指針可以先后指向不同的類對象,從而調用同一類族中不同類的虛函數。由于動態關聯是在編譯以后的運行階段進行的,因此也稱為滯后關聯(late binding) 。
C++虛析構函數詳解
當派生類的對象從內存中撤銷時一般先調用派生類的析構函數,然后再調用基類的析構函數。但是,如果用new運算符建立了臨時對象,若基類中有析構函數,并且定義了一個指向該基類的指針變量。在程序用帶指針參數的delete運算符撤銷對象時,會發生一個情況:系統會只執行基類的析構函數,而不執行派生類的析構函數。
[例] 基類中有非虛析構函數時的執行情況。為簡化程序,只列出最必要的部分。
- #include <iostream>
- using namespace std;
- class Point //定義基類Point類
- {
- public:
- Point( ){} //Point類構造函數
- ~Point(){cout<<"executing Point destructor"<<endl;} //Point類析構函數
- };
- class Circle:public Point //定義派生類Circle類
- {
- public:
- Circle( ){} //Circle類構造函數
- ~Circle( ){cout<<"executing Circle destructor"<<endl;} //Circle類析構函數
- private:
- int radius;
- };
- int main( )
- {
- Point *p=new Circle; //用new開辟動態存儲空間
- delete p; //用delete釋放動態存儲空間
- return 0;
- }
這只是一個示意的程序。p是指向基類的指針變量,指向new開辟的動態存儲空間,希望用detele釋放p所指向的空間。但運行結果為:
- executing Point destructor
表示只執行了基類Point的析構函數,而沒有執行派生類Circle的析構函數。
如果希望能執行派生類Circle的析構函數,可以將基類的析構函數聲明為虛析構函數,如:
- virtual ~Point(){cout<<″executing Point destructor″<<endl;}
程序其他部分不改動,再運行程序,結果為:
- executing Circle destructor
- executing Point destructor
先調用了派生類的析構函數,再調用了基類的析構函數,符合人們的愿望。
當基類的析構函數為虛函數時,無論指針指的是同一類族中的哪一個類對象,系統會采用動態關聯,調用相應的析構函數,對該對象進行清理工作。
如果將基類的析構函數聲明為虛函數時,由該基類所派生的所有派生類的析構函數也都自動成為虛函數,即使派生類的析構函數與基類的析構函數名字不相同。
最好把基類的析構函數聲明為虛函數。這將使所有派生類的析構函數自動成為虛函數。這樣,如果程序中顯式地用了delete運算符準備刪除一個對象,而delete運算符的操作對象用了指向派生類對象的基類指針,則系統會調用相應類的析構函數。
虛析構函數的概念和用法很簡單,但它在面向對象程序設計中卻是很重要的技巧。
專業人員一般都習慣聲明虛析構函數,即使基類并不需要析構函數,也顯式地定義一個函數體為空的虛析構函數,以保證在撤銷動態分配空間時能得到正確的處理。
構造函數不能聲明為虛函數。這是因為在執行構造函數時類對象還未完成建立過程,當然談不上函數與類對象的綁定。
新聞熱點
疑難解答