【C++】—多型(上)動態多型
一、多型
1、 概念:同一事物表現出的多種形態,同一操作作用於不同的物件,可以有不同的解釋,產生不同的執行結果。在執行時,可以通過指向基類的指標,來呼叫實現派生類中的方法。
2、 舉例子:
#include<windows.h>
class WashRoom
{
public:
void GoToManWashRoom()
{
cout << "Man ---> Please Left" << endl;
}
void GoToWomanWashRoom()
{
cout << "Woman ------> Please Right" << endl;
}
};
class Person
{
public:
virtual void GoToWashRoom(WashRoom& wc) = 0;
};
class Man :public Person
{
public:
virtual void GoToWashRoom(WashRoom& wc)
{
wc.GoToManWashRoom();
}
};
class Woman :public Person
{
public:
virtual void GoToWashRoom (WashRoom& wc)
{
wc.GoToWomanWashRoom();
}
};
void TestWashRoom()
{
WashRoom wc;
Person* p = NULL;
for (int i = 0; i <10; ++i)
{
if ((rand()) & 0x01)
p = new Man;
else
p = new Woman;
p->GoToWashRoom(wc);
delete p;
Sleep(1000 );
}
}
int main()
{
TestWashRoom();
return 0;
}
3、 多型的分類
(1)靜態多型:編譯器在編譯期間來確定程式的行為(確定具體呼叫哪個函式)
A:函式過載
B:泛型程式設計
(2)動態多型:程式執行時來確定程式的行為(確定具體呼叫哪個函式)
4、 動態多型實現條件
(1) 基類中必須包含虛擬函式,在派生類中必須對基類的虛擬函式進行重寫
(2) 必須通過基類指標或引用呼叫虛擬函式
//1、基類中必須包含虛擬函式,且派生類一定要對基類中的虛擬函式進行重寫(原型一致)
class Base
{
public:
virtual void TestFunc1()
{
cout << "Base::TestFunc1()" << endl;
}
virtual void TestFunc2()
{
cout << "Base::TestFunc2()" << endl;
}
void TestFunc3()//基類無虛擬關鍵字,派生類有
{
cout << "Base::TestFunc3()" << endl;
}
virtual void TestFunc4(int)//派生類中是char,引數型別不一樣
{
cout << "Base::TestFunc4()" << endl;
}
/*virtual int TestFunc5()
{
cout << "Base::TestFunc5()" << endl;
return 0;
}*/
//協變—基類(派生類)虛擬函式返回基類(派生類)指標或引用
virtual Base* TestFunc5()
{
cout << "Base::TestFunc5()" << endl;
return this;
}
virtual Base& TestFunc6()
{
cout << "Base::TestFunc6()" << endl;
return *this;
}
~Base()//解構函式
{
cout << "Base::~Base()" << endl;
}
};
class Derived : public Base
{
public:
virtual void TestFunc1()
{
cout << "Derived::TestFunc1()" << endl;
}
void TestFunc2()//派生類無虛擬關鍵字,基類有
{
cout << "Derived::TestFunc2()" << endl;
}
virtual void TestFunc3()
{
cout << "Derived::TestFunc3()" << endl;
}
//沒有構成重寫(引數型別不同)
virtual void TestFunc4(char)//基類中是int 引數型別不同
{
cout << "Derived::TestFunc4()" << endl;
}
//virtual char TestFunc5()//不能重寫,返回型別不同
//{
// cout << "Derived::TestFunc5()" << endl;
// return 0;
//}
//協變—基類(派生類)虛擬函式返回基類(派生類)指標或引用
virtual Derived* TestFunc5()
{
cout << "Derived::TestFunc5()" << endl;
return this;
}
virtual Derived& TestFunc6()
{
cout << "Derived::TestFunc6()" << endl;
return *this;
}
virtual ~Derived()
{
cout << "Derived::~Derived()" << endl;
}
};
//2、通過基類物件的指標或引用呼叫虛擬函式
void TestVirtualFunc(Base& b)
{
b.TestFunc1();
b.TestFunc2();
b.TestFunc3();
b.TestFunc4(10);
b.TestFunc4('a');
}
int main()
{
Base* pb = new Derived;//基類的指標,指向派生類的物件
delete pb;//如果呼叫基類裡的析構,則沒有重寫;反之
Base b;
Derived d;
//如果引用的是基類的物件,呼叫基類裡對應的虛擬函式
//如果引用的是派生類的物件,則呼叫派生類裡對應的虛擬函式
TestVirtualFunc(b);
TestVirtualFunc(d);
return 0;
}
返回值不同不能重寫
5、 重寫
(1)基類中的函式必須為虛擬函式
(2)派生類中重寫的虛擬函式必須與基類的虛擬函式型別保持一致(返回值、函式名字(引數列表))
例外:
協變:基類中虛擬函式返回基類物件的指標或引用,派生類中虛擬函式返回派生類物件的指標或引用——返回值型別不同
解構函式——基類和派生類中函式的名字不同
(3)基類中虛擬函式和派生類虛擬函式的訪問限定符可以不同
——過載、重寫、重定義
6、程式碼實現多型
class Base
{
public:
virtual void TestFunc1()
{
cout << "Base::TestFunc1()" << endl;
}
virtual void TestFunc2()
{
cout << "Base::TestFunc2()" << endl;
}
virtual void TestFunc3()
{
cout << "Base::TestFunc3()" << endl;
}
int _b;
};
class Derived :public Base
{
public:
virtual void TestFunc2()
{
cout << "Derived::TestFunc2()" << endl;
}
virtual void TestFunc3()
{
cout << "Derived::TestFunc3()" << endl;
}
int _d;
};
void TestVirtualFunc(Base& b)
{
b. TestFunc1();
b. TestFunc2();
}
int main()
{
Base b;
Derived d;
TestVirtualFunc(b);
TestVirtualFunc(d);
return 0;
}
7、動態多型呼叫原理
多型的條件已經完全滿足
1)從物件的前4個位元組中取虛表地址
2) 傳參(虛擬函式形參+this)
3) 從虛表中取虛擬函式地址
4) 呼叫該虛擬函式
二、抽象類
1、概念
在成員函式(必須為虛擬函式)的形參列表後面寫上=0,則成員函式為純虛擬函式。包含純虛擬函式的類叫做抽象類(也叫介面類),抽象類不能例項化出物件。純虛擬函式在派生類中重新定義以後,派生類才能例項化出物件。
class WC;
class Person
{
public:
virtual void GoToWC(WC& c) = 0;//純虛擬函式
};
class Man :public Person
{
public:
virtual void GoToWC(WC& c)
{
//
}
};
int main()
{
Person* p;
Man m;
return 0;
}
2、總結
(1)派生類重寫基類的虛擬函式實現多型,要求函式名、引數列表、返回值完全相同(協變除外)
(2)基類中定義了虛擬函式,在派生類中該函式始終保持虛擬函式的特性
(3)只有類的非靜態成員函式才能定義為虛擬函式,靜態成員函式不能定義為虛擬函式
(4)如果在類外定義虛擬函式,只能在宣告函式時加virtual關鍵字,定義時不用加
(5)建構函式不能定義為虛擬函式,雖然可以將operator=定義為虛擬函式,但最好不要這麼做,使用時容易混淆(?)
(6)不要在建構函式和解構函式中呼叫虛擬函式,在建構函式和解構函式中,物件是不完整的,可能會出現未定義的行為
(7)最好將基類的解構函式宣告為虛擬函式。(解構函式比較特殊,因為派生類的解構函式跟基類的解構函式名稱不一樣,但是構成覆蓋,這裡編譯器做了特殊處理)
(8)虛表是所有類物件例項共用的
class Base
{
public:
virtual void TestFunc1()
{
cout << "Base::TestFunc1()" << endl;
}
int _b;
};
int main()
{
cout << sizeof(Base) << endl;//8
Base b;
b._b = 1;
return 0;
}