原文地址: http://www.cnblogs.com/xd502djj/archive/2010/09/22/1832912.html
Virtual是C++ OO機制中很重要的一個關鍵字。只要是學過C++的人都知道在類Base中加了Virtual關鍵字的函數就是虛擬函數(例如函數print),於是在Base的派生類Derived中就可以通過重寫虛擬函數來實現對基類虛擬函數的覆蓋。當基類Base的指針point指向派生類Derived的對象時,對point的print函數的調用實際上是調用了Derived的print函數而不是Base的print函數。這是面向對象中的多態性的體現。(關於虛擬機制是如何實現的,參見Inside the C++
Object Model ,Addison Wesley 1996)
- class Base
- {
- public:Base(){}
- public:
- virtual void print(){cout<<"Base";}
- };
- class Derived:public Base
- {
- public:Derived(){}
- public:
- void print(){cout<<"Derived";}
- };
- int main()
- {
- Base *point=new Derived();
- point->print();
- }
- Output:
- Derived
這也許會使人聯想到函數的重載,但稍加對比就會發現兩者是完全不同的:
(1) 重載的幾個函數必須在同一個類中;
覆蓋的函數必須在有繼承關係的不同的類中
(2) 覆蓋的幾個函數必須函數名、參數、返回值都相同;
重載的函數必須函數名相同,參數不同。參數不同的目的就是爲了在函數調用的時候編譯器能夠通過參數來判斷程序是在調用的哪個函數。這也就很自然地解釋了爲什麼函數不能通過返回值不同來重載,因爲程序在調用函數時很有可能不關心返回值,編譯器就無法從代碼中看出程序在調用的是哪個函數了。
(3) 覆蓋的函數前必須加關鍵字Virtual;
重載和Virtual沒有任何瓜葛,加不加都不影響重載的運作。
關於C++的隱藏規則:
我曾經聽說過C++的隱藏規則:
(1)如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual
關鍵字,基類的函數將被隱藏(注意別與重載混淆)。
(2)如果派生類的函數與基類的函數同名,並且參數也相同,但是基類函數沒有virtual
關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)。
- #include <iostream.h>
- class Base
- {
- public:
- virtual void f(float x){ cout << "Base::f(float) " << x << endl; }
- void g(float x){ cout << "Base::g(float) " << x << endl; }
- void h(float x){ cout << "Base::h(float) " << x << endl; }
- };
- class Derived : public Base
- {
- public:
- virtual void f(float x){ cout << "Derived::f(float) " << x << endl; }
- void g(int x){ cout << "Derived::g(int) " << x << endl; }
- void h(float x){ cout << "Derived::h(float) " << x << endl; }
- };
- void main(void)
- {
- Derived d;
- Base *pb = &d;
- Derived *pd = &d;
- // Good : behavior depends solely on type of the object
- pb->f(3.14f); // Derived::f(float) 3.14
- pd->f(3.14f); // Derived::f(float) 3.14
- // Bad : behavior depends on type of the pointer
- pb->g(3.14f); // Base::g(float) 3.14
- pd->g(3.14f); // Derived::g(int) 3 (surprise!)
- // Bad : behavior depends on type of the pointer
- pb->h(3.14f); // Base::h(float) 3.14 (surprise!)
- pd->h(3.14f); // Derived::h(float) 3.14
- }
所以並沒有所謂的隱藏規則,雖然《高質量C++/C 編程指南》是本很不錯的書,可大家不要迷信哦。記住“只有在通過基類指針或引用間接指向派生類子類型時多態性纔會起作用”。
純虛函數:
C++語言爲我們提供了一種語法結構,通過它可以指明,一個虛擬函數只是提供了一個可被子類型改寫的接口。但是,它本身並不能通過虛擬機制被調用。這就是純虛擬函數(pure virtual function)。 純虛擬函數的聲明如下所示:
- class Query {
- public:
- // 聲明純虛擬函數
- virtual ostream& print( ostream&=cout ) const = 0;
- // ...
- };
包含(或繼承)一個或多個純虛擬函數的類被編譯器識別爲抽象基類。試圖創建一個抽象基類的獨立類對象會導致編譯時刻錯誤。(類似地通過虛擬機制調用純虛擬函數也是錯誤的例如)
- // Query 聲明瞭純虛擬函數
- // 所以, 程序員不能創建獨立的 Query 類對象
- // ok: NameQuery 中的 Query 子對象
- Query *pq = new NameQuery( "Nostromo" );
- // 錯誤: new 表達式分配 Query 對象
- Query *pq2 = new Query;
如果只知道virtual加在函數前,那對virtual只瞭解了一半,virtual還有一個重要用法是virtual public,就是虛擬繼承。虛擬繼承在C++ Primer中有詳細的描述,下面稍作修改的闡釋一下:
在缺省情況下C++中的繼承是“按值組合”的一種特殊情況。當我們寫
- class Bear : public ZooAnimal { ... };
- class PolarBear : public Bear { ... };
- class iostream :public istream, public ostream { ... };
C++語言的解決方案是,提供另一種可替代按“引用組合”的繼承機制虛擬繼承(virtual inheritance )在虛擬繼承下只有一個共享的基類子對象被繼承而無論該基類在派生層次
中出現多少次共享的基類子對象被稱爲虛擬基類。
通過用關鍵字virtual 修政一個基類的聲明可以將它指定爲被虛擬派生。例如,下列聲明使得ZooAnimal 成爲Bear 和Raccoon 的虛擬基類:
- // 關鍵字 public 和 virtual
- // 的順序不重要
- class Bear : public virtual ZooAnimal { ... };
- class Raccoon : virtual public ZooAnimal { ... };