純虛函數
純虛函數是一種特殊的虛函數,在許多情況下,在基類中不能對虛函數給出有意義的實現,而把它聲明為純虛函數,它的實現留給該基類的派生類去做。這就是純虛函數的作用。純虛函數的存在是為了更方便使用多態特性。它的一般格式如下:
1 class A {
2 public: 3 A(); 4 virtual ~A(); 5 void f1(); 6 virtual void f2(); 7 virtual void f3()=0; 8 }; 9 class B:public A{ 10 public: 11 B(); 12 virtual ~B(); 13 void f1(); 14 virtual void f2(); 15 virtual void f3(); 16 }; 17 int main(int argc,char * argv[]) { 18 A *m_j = new B(); 19 m_j -> f1(); 20 m_j -> f2(); 21 m_j -> f3(); 22 delete m_j; 23 return 0; 24 }
1 class Person
2 { 3 vi rtual voi d Di splay () = 0; // 純虛函數 4 protected : 5 stri ng _name ; // 姓名 6 } ; 7 class Student : publi c Person 8 { } ;
總結:
1. 虛函數的定義形式:virtual {method body} 純虛函數的定義形式:virtual { } = 0;
2. 虛函數和純虛函數可定義在同一個類(class)中,含有純虛函數的類是抽象類(abstract class),而含有虛函數的類則不是。
3. 對抽象類進行實例化將會報錯,因為抽象基類(ABC)是不能被直接調用的,必須被子類繼承重寫以后,根據要求調用其子類的方法,因為純虛函數在基類(base class)只有聲明而沒有定義。目的是提供一個統一的接口。虛函數可以被直接使用,也可以被子類(sub class)重寫后以多態的形式調用。父類和子類都有各自的版本,調用的時候動態綁定。
4. 在虛函數和純虛函數的定義中不能有static標識符,原因很簡單,被static修飾的函數在編譯時候要求前期bind,然而虛函數卻是動態綁定(run-time bind),而且被兩者修飾的函數生命周期(life recycle)也不一樣。
5. 虛函數必須實現,如果不實現,編譯器將報錯,錯誤提示為:error LNK****: unresolved external symbol "public: virtual void __thiscall
ClassName::virtualFunctionName(void)"
6. 實現了純虛函數的基類,該純虛函數可以被子類重寫成虛函數,子類的子類即孫子類可以覆蓋該虛函數,由多態方式調用的時候動態綁定。
7. 多態性是指相同對象收到不同消息或不同對象收到相同消息時產生不同的實現動作。C++支持兩種多態性:編譯時多態性,運行時多態性。
a.編譯時多態性:通過重載函數實現。
b 運行時多態性:通過虛函數實現。
重載、重寫、重定義
1.什么是函數重載
重載,簡單說,就是函數或者方法有相同的名稱,但是參數列表不同的情形,這樣的同名不同參數的函數或者方法之間,互相稱之為重載函數或者方法。一般是用於在一個類內實現若干重載的方法,這些方法的名稱相同而參數形式不同。
重載的規則:
1、在使用重載時只能通過相同的方法名、不同的參數形式實現。不同的參數可以是參數類型,參數個數,參數順序(參數類型必須不一樣);
2、不能通過訪問權限、返回類型、拋出的異常進行重載;
3、方法的異常類型和數目不會對重載造成影響;
成員函數被重載的特征:
(1)相同的范圍(在同一個類中);
(2)函數名字相同;
(3)參數不同;
(4)virtual 關鍵字可有可無 。
其中與其他另外兩個概念最大的區別是:函數重載在同一個作用域內。
因為首先函數重載的第一個條件就沒有滿足,即:在相同的范圍中(在同一個類中),派生類和基類是兩個不同的類域,即不是同一個作用域,所以在繼承中,基類和派生類之間永遠不可能進行函數重載。
1 class Base
2 { 3 public: 4 Base(int data = 0) 5 :b(data) 6 { 7 cout << "Base()" << endl; 8 } 9 ~Base() 10 { 11 cout << "~Base()" << endl; 12 } 13 void B() 14 { 15 cout << "Base::B()" << endl; 16 } 17 void B(int b) 18 { 19 cout << "Base::B(int)" << endl; 20 } 21 //B()與B(int b)構成了函數重載 22 //因為上面兩個函數是在同一作用域中 23 int b; 24 }; 25 class Derive :public Base 26 { 27 public: 28 Derive() 29 { 30 cout << "Derive()" << endl; 31 } 32 ~Derive() 33 { 34 cout << "~Derive()" << endl; 35 } 36 void B(int a, int b) 37 { 38 cout << "Derive::B(int,int)" << endl; 39 } 40 //不會與Base類中的兩個B名的函數構成重載 41 //因為作用域不同 42 };
2.什么是重寫
子類可繼承父類中的方法,而不需要重新編寫相同的方法。但有時子類並不想原封不動地繼承父類的方法,而是想作一定的修改,這就需要采用方法的重寫。方法重寫又稱方法覆蓋。重寫即覆蓋,是指派生類函數覆蓋基類函數。例如,假設動物類存在"跑"的方法,從中派生出馬和狗,馬和狗的跑得形態是各不相同的,因此同樣方法需要兩種不同的實現,這就需要"重新編寫"基類中的方法。"重寫"基類方法就是修改它的實現或者說在派生類中重新編寫。
1、重寫方法的參數列表必須完全與被重寫的方法的相同,否則不能稱其為重寫。
2、重寫方法的訪問修飾符一定要大於被重寫方法的訪問修飾符(public>protected>default>private)。
3、重寫的方法的返回值與被重寫的方法的返回值可能不同,(協變)可能是基類返回基類的指針或引用,子類返回子類的指針或引用;
4、重寫的方法所拋出的異常必須和被重寫方法的所拋出的異常一致,或者是其子類;
5、被重寫的方法不能為private,否則在其子類中只是新定義了一個方法,並沒有對其進行重寫。
(1)不同的范圍(分別位於派生類與基類);
(2)函數名字相同;
(3)參數相同;
(4)基類函數必須有virtual 關鍵字。
對象調用函數的情況
class Base
{
public: Base(int data = 1) :b(data) { cout << "Base()" << endl; } ~Base() { cout << "~Base()" << endl; } virtual void Test() { cout << "Base::Test()" << endl; } int b; }; class Derive :public Base { public: Derive(int data = 2) :d(data) { cout << "Derive()" << endl; } ~Derive() { cout << "~Derive()" << endl; } void Test() { cout << "Derive::Test()" << endl; } int d; }; int main() { Derive d; d.Test(); return 0; }
在上面的代碼中,分別在基類和派生類中定義了同名同參數的函數Test(),看一下運行結果,看會調用基類的函數還是派生類的函數:
Base()
Derive()
Derived::Test()
運行結果可以表明: 這里的Test()函數發生了函數覆蓋。
指針或引用調用函數的情況
1 class Base
2 { 3 public: 4 Base(int data = 1) 5 :b(data) 6 { 7 cout << "Base()" << endl; 8 } 9 ~Base() 10 { 11 cout << "~Base()" << endl; 12 } 13 virtual void Test() 14 { 15 cout << "Base::Test()" << endl; 16 } 17 int b; 18 }; 19 class Derive :public Base 20 { 21 public: 22 Derive(int data = 2) 23 :d(data) 24 { 25 cout << "Derive()" << endl; 26 } 27 ~Derive() 28 { 29 cout << "~Derive()" << endl; 30 } 31 void Test() 32 { 33 cout << "Derive::Test()" << endl; 34 } 35 int d; 36 }; 37 38 int main() 39 { 40 Base *pb; 41 Derive d; 42 pb = &d; 43 pb->Test(); 44 return 0; 45 }
運行結果同上
多態的本質:不是重載聲明而是覆蓋。 虛函數調用方式:通過基類指針或引用,執行時會根據指針指向的對象的類,決定調用哪個函數。重寫總結:
3.什么是重定義
子類重新定義父類中有相同名稱的非虛函數 ( 參數列表可以不同 ) 。
如果一個類,存在和父類相同的函數,那么,這個類將會覆蓋其父類的方法,除非你在調用的時候,強制轉換為父類類型,否則試圖對子類和父類做類似重載的調用是不能成功的。
(1)如果派生類的函數與基類的函數同名,並且參數也相同,但是基類函數沒有virtual 關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)
(2)如果派生類的函數與基類的函數同名,但是參數不相同。此時,不論有無virtual關鍵字,基類的函數將被隱藏(注意別與重載混淆)。
1 class Base
2 { 3 public: 4 Base(int data = 1) 5 :b(data) 6 { 7 cout << "Base()" << endl; 8 } 9 ~Base() 10 { 11 cout << "~Base()" << endl; 12 } 13 void Test() 14 { 15 cout << "Base::Test()" << endl; 16 } 17 int b; 18 }; 19 class Derive :public Base 20 { 21 public: 22 Derive(int data = 2) 23 :d(data) 24 { 25 cout << "Derive()" << endl; 26 } 27 ~Derive() 28 { 29 cout << "~Derive()" << endl; 30 } 31 void Test() 32 { 33 cout << "Derive::Test()" << endl; 34 } 35 int d; 36 }; 37 38 int main() 39 { 40 Derive d; 41 d.Test(); 42 return 0; 43 }
在我的另一篇文章C++中的繼承(3)作用域與重定義,賦值兼容規則中對重定義也有解釋。
總結:
1.函數重載必須是在同一作用域的,在繼承與多態這里,在基類與派生類之間是不能進行函數重載。
2.函數覆蓋是多態的本質在基類中的虛函數,在派生類定義一個同名同參數的函數,就可以用派生類新定義的函數對基類函數進行覆蓋。
3.函數隱藏是發生在基類和派生類之間的,當函數同名但是不同參數的時候,不論是不是虛函數,都會發生函數隱藏。