C++語言學習(十一)——多態
一、多態簡介
C++中的多態(polymorphism)是指由繼承而產生的相關的不同的類,其對象對同一消息會作出不同的響應。
多態性是面向對象程序設計的一個重要特征,能增加程序的靈活性。可以減輕系統升級,維護,調試的工作量和復雜度。
多態是一種不同層次分類下的重要聯系,是一種跨層操作。
二、多態實現的前提
賦值兼容規則是指在需要基類對象的任何地方都可以使用公有派生類的對象來替代。
賦值兼容是一種默認行為,不需要任何的顯式的轉化步驟,只能發生在public繼承方式中,是多態實現的前提條件。
賦值兼容規則中所指的替代包括以下的情況:
A、子類對象可以直接賦值給父類對象
B、子類對象可以直接初始化父類對象
D、父類指針可以直接指向子類對象
當使用父類指針(引用)指向子對象時,子類對象退化為父類對象,只能訪問父類中定義的成員,可以直接訪問被子類覆蓋的同名成員。
#include <iostream> using namespace std; class Parent { public: int m; Parent(int a) { m = a; } void print() { cout << "Parent m = " << m << endl; } }; class Child : public Parent { public: int m; Child(int a):Parent(a) { m = a; } void print() { cout << "Child m = " << m << endl; } }; int main() { Parent p(0); Child c(0); Parent p1(c); Parent* pp = &c;//指向子類對象的父類指針退化為父類對象 Parent& rp = c;//指向子類對象的父類引用退化為父類對象 pp->print(); //Parent m = 0 rp.print(); //Parent m = 0 //Child cc = static_cast<Child>(p);//需要轉換構造函數支持 Child* pc = static_cast<Child*>(&p); pc->print();//Child m = xxxx return 0; }
在替代後,派生類對象就可以作為基類的對象使用,但只能使用從基類繼承的成員。
父類也可以通過強轉的方式轉化為子類,但存在訪問越界的風險。
子類中可以重定義父類中已經存在的成員函數,即函數重寫。
當函數重寫遇到賦值兼容時,編譯器只能根據指針的類型判斷所指向的對象,根據賦值兼容原則,編譯器認為父類指針指向的是父類對象,只能調用父類中定義的同名函數。
面向對象編程中,通常需要根據實際的對象類型判斷如何調用重寫函數。當父類指針指向父類對象,則調用父類定義的函數;當父類指針指向的是子類對象時,需要調用子類定義的函數;當父類引用對父類對象進行引用時,調用父類對象定義的函數;當父類引用對子類對象進行引用時,調用子類定義的函數。
三、多態形成的條件
1、多態形成的條件
根據父類指針指向的實際對象類型決定調用的函數,即多態。多態中,父類指針(引用)指向父類對象則調用父類中定義的函數,父類指針(引用)指向子類對象時調用子類對象的函數。
C++通過virtual關鍵字對多態進行支持,被virtual聲明的函數被重寫後具有多態屬性。
多態形成的條件:
A、父類中有虛函數。
B、子類override(覆寫)父類中的虛函數。
C、通過己被子類對象賦值的父類指針,調用共用接口。
2、虛函數
虛函數的聲明語法如下:
virtual 函數聲明;
虛函數的使用規則如下:
A、在父類中用 virtual 聲明成員函數為虛函數。類外實現虛函數時,不必再加virtual。
B、在派生類中重定義父類中已經存在的成員函數稱為函數覆寫,要求函數名,返值類型,函數參數個數及類型全部匹配,並根據派生類的需要重新定義函數體。
C、當一個成員函數被聲明為虛函數後,其派生類中完全相同的函數也為虛函數,派生類中的虛函數可以加virtual關鍵字,也可以不加。
D、定義一個父類對象指針,使其指向其子類的對象,通過父類指針調用虛函數,此時調用的是子類的同名函數。
E、構造函數不能為虛函數,在構造函數執行完畢後虛函數表指針才能被正確初始化。析構函數可以為虛函數,定義一個父類指針並使用new創建的子類對象初始化,使用delete釋放父類指針的堆空間時,只會調用父類的析構函數,不會調用子類的析構函數,會造成內存泄漏,父類析構函數聲明為虛函數可以避免該問題。一般來說需要將析構函數聲明為虛函數。構造函數執行時,虛函數表指針未被正確初始化,因此構造函數不可能發生多態;析構函數函數執行時,虛函數表指針已經被銷毀,因此析構函數也不可能發生多態。構造函數和析構函數中只能調用當前類中定義的函數版本。
#include <iostream>
using namespace std;
class Parent
{
public:
int mi;
void add(int i)
{
mi += i;
}
void add(int a, int b)
{
mi += (a + b);
}
virtual void print()
{
cout << "Parent" << endl;
}
};
class Child : public Parent
{
public:
int mi;
//函數重寫
void add(int x, int y)
{
mi += (x + y);
}
//函數重寫
void print()
{
cout << "Child" << endl;
}
};
int main(int argc, char *argv[])
{
Child child;
child.add(1,2);//調用子類函數
child.Parent::add(1);//調用父類函數
child.Parent::add(1,2);//調用父類函數
Parent p = child;
p.add(1);
p.add(1,2);
Parent& rp = child;
rp.add(1);
rp.add(1,2);
rp.print();//Child
Parent* pp = &child;
pp->add(1);
pp->add(1,2);
pp->print();//Child
return 0;
}
3、純虛函數
純虛函數的聲明語法如下:
virtual 函數聲明 = 0;
純虛函數的使用規則如下:
A、含有純虛函數的類,稱為抽象基類,不可實例化,即不能創建對象,存在的意義就是被繼承,提供類族的公共接口,Java中稱為 interface。
B、純虛函數只有聲明,沒有實現。
C、如果一個類中聲明了純虛函數,而在派生類中沒有對純虛函數進行實現,則該虛函數在派生類中仍然為純虛函數,派生類仍然為抽象基類。
4、虛函數的使用規則
虛函數的使用規則如下:
A、只有類的成員函數才能聲明為虛函數。虛函數僅適用於有繼承關系的類對象,所以普通函數不能聲明為虛函數。
B、靜態成員函數不能是虛函數。靜態成員函數不受對象的捆綁,只有類的信息。
C、內聯函數不能是虛函數。
D、構造函數不能是虛函數。構造時,對象的創建尚未完成。構造完成後,才能算一個名符其實的對象。
E、析構函數可以是虛函數且通常聲明為虛函數
F、含有虛函數的類,析構函數也必須聲明為虛函數。在delete父類指針的時候,會調用子類的析構函數。
四、多態的意義
多態的意義如下:
A、在程序運行過程中展現出的動態特性
B、函數重寫必須多態實現,否則沒有意義
C、多態是面向對象組件化程序設計的基礎特性
D、多態是一種跨層操作
靜態聯編是在程序的編譯期間就能確定具體的函數調用,如函數重載。
動態聯編是在程序實際運行時才能確定具體的函數調用,如函數重寫。
#include <iostream>
using namespace std;
class Parent
{
public:
int mi;
virtual void add(int i)
{
mi += i;
}
virtual void add(int a, int b)
{
mi += (a + b);
}
virtual void print()
{
cout << "Parent" << endl;
}
};
class Child : public Parent
{
public:
int mi;
//函數重寫
void add(int x, int y)
{
mi += (x + y);
}
//函數重寫
void print()
{
cout << "Child" << endl;
}
};
int main(int argc, char *argv[])
{
Child child;
child.add(1,2);//靜態聯編
child.Parent::add(1);//靜態聯編
child.Parent::add(1,2);//靜態聯編
Parent p = child;
p.add(1);//靜態聯編
p.add(1,2);//靜態聯編
p.print();//靜態聯編
cout << endl;
Parent& rp = child;
rp.add(1);//靜態聯編
rp.add(1,2);//動態聯編
rp.print();//動態聯編
Parent* pp = &child;
pp->add(1);//靜態聯編
pp->add(1,2);//動態聯編
pp->print();//動態聯編
return 0;
}
C++語言學習(十一)——多態