一.什麼是多態
多態與封裝,繼承一起構成了面向對象的3大特性。多態指向不同對象發送同一消息,不同的對象會產生不同的行爲。也就是說每個對象用自己的方式去響應共同的消息。C++有倆中形式的多態。如上課鈴打了,不同班級的同學走向不同的教室。
編譯時的多態性,即靜態聯編:
程序在編譯之前就可以確定的多態性,通過重載機制來實現的,可以是函數重載,也可以是運算符重載。
運行時多態性,即動態聯編:
必須在運行時纔可以確定的多態性,通過繼承和虛函數來實現。使用virtual關鍵字修飾類的成員函數時,指明該函數爲虛函數,派生類需要重新實現,編譯器將實現動態綁定。
如:virtual double area() { return 0; }
二..虛函數的定義
爲了實現某種功能而假設的函數稱爲虛函數。
const double IP = 3.14159;
class Cpint
{
public:
Cpint(double x,double y)
{
this->x = x;
this->y = y;
}
virtual double area()
{
return 0;
}
private:
double x,y;
};
class Ccircle : public Cpint
{
public:
Ccircle(double x,double y,double radius) : Cpint(x,y)
{
this->radius = radius;
}
double area()
{
return IP * radius * radius;
}
private:
double radius;
};
void main()
{
Cpint pint(4.0,4.0);
Ccircle circle(5.0,6.0,10);
cout <<"area of Cpint is "<<pint.area()<<endl;
cout<<"area of Ccircle is "<<circle.area()<<endl;
Cpint *ptr_pint = &pint;
cout<<"area of ptr_pint is "<<ptr_pint->area()<<endl;
Ccircle *ptr_circle = &circle;
cout<<"area of ptr_circle is "<<ptr_circle->area()<<endl;
ptr_pint = &circle;
cout<<"area of Ccircle is "<<ptr_pint->area()<<endl;
system("pause");
}
但是如果要讓程序實現下面這種結果:
就是要Cpint的指針指向ptr_Ccircle的成員函數area的地址,爲了給倆個函數一個新的標識符,使他們區分開來,引入關鍵字:virtual,
將這種函數稱爲虛函數:
Virtual double area() {return 0;}
如果派生類沒有改寫繼承類的虛函數,則函數指針調用基類的虛函數。如果派生類改寫了基類的虛函數,編譯器將重新爲派生類的虛函數建立地址,函數指針調用改寫過的虛函數。
總結:如果派生類中有與基類同名的成員函數,並且基類指針指向派生類的對象,那麼基類指針調用的函數是派生類的函數
三..動態聯編的工作機制。
當編譯器編譯遇到virtual關鍵字的時候,將自動爲包含virtual的函數建立一張虛擬函數表,在這個虛函數表中依次存放了特定虛函數的地址。同時在每個帶有虛函數的類中放置一個指針。如果基類的成員函數定義爲虛函數,那麼他所在的派生類中也保持爲虛函數,(virtual在派生類中可以省略)
對於虛函數有以下幾點說明:
1.基類成員函數定義爲虛函數後,要使達到動態聯編的效果,派生類和基類對應的函數名,返回值類型,參數個數和類型也必須相同。
2.基類中虛函數的virtual關鍵字不能省略,派生類中虛函數的關鍵字可以省略。
3.運行時虛函數必須通過基類對象的引用或基類對象的指針調用虛函數才能實現。
4.虛函數必須是類的成員函數,不能是友元函數也不能是靜態成員函數。
5.不能將構造函數定義成虛函數(構造函數執行時,對象還沒有完全構造好),但可以將析構函數定義成虛函數。
四.虛析構函數
如果基類析構函數爲虛析構函數,則釋放基類指針的時候會調用基類和派生類中所有的析構函數,派生類對象的所有內存空間都將被釋放。因此c++中析構函數通常爲虛析構函數。
定義方法爲:
Vietual ~ 類名();
五.純虛函數與抽象類
純虛函數是在聲明虛函數時被“初始化”爲0的函數,純虛函數只有函數的名字而不具備函數的功能,不能被調用。至少含有一個純虛函數的類稱爲抽象類
純虛函數:它的作用是在基類中爲其派生類保留一個函數的名字,以便派生類根據需要對它進行定義。如果在基類中,沒有保留函數的名字,無法實現多態。純虛函數的一般形式爲:
Class 類名
{
Virtual 類型 函數名(參數表)= 0;//純虛函數
.......
};
注意:1.純虛函數沒有函數體
2.“=0”只是形式上的作用,告訴編譯器此爲純虛函數。
抽象類:class 類名
{
Public :
Virtual 返回值類型 函數名(參數表)= 0;
其他函數的而聲明;
}
注意:1.抽象類只能用作其他類的基類,抽象類不能建立對象
2.抽象類不能用作函數參數類型、函數返回類型或顯示轉換類型。
3.可以聲明抽象類的指針和引用。
抽象類實例:
class CPerson //抽象類型
{
public:
virtual void PrintInfo() //基類中的虛構函數
{
cout <<"人類\n";
}
virtual void DisplaySalary(int m,double s) = 0;//抽象類中的純虛函數
};
class CWorker :public CPerson
{
public:
void PrintInfo() //在派生類中重新定義
{
cout <<"工人\n";
}
void DisplaySalary(int m,double s)
{
cout <<"工人全年的工資是:"<<m*s<<endl;
}
private:
int kindofwork;
};
class CTeacher:public CPerson
{
public:
void PrintInfo()
{
cout << "教師\n";
}
void DisplaySalary(int m,double s)
{
cout <<"教師全年的工資:"<<m*s << endl;
}
private:
int subject;
};
class CDriver:public CPerson
{
public:
void PrintInfo()
{
cout <<"司機\n";
}
void DisplaySalary(int m,double s)
{
cout <<"司機的全年工資是:"<<m*s<<endl;
}
private:
int subject;
};
void main()
{
CWorker worker; //分別對三個類的對象進行聲明
CTeacher teacher;
CDriver driver;
CPerson *person; //指向父類的指針變量person
person = &worker; //指向派生類,獲取的是指向派生類的虛表指針
person -> PrintInfo();
person -> DisplaySalary(12,1800.35);
person = &teacher;
person -> PrintInfo();
person -> DisplaySalary(12,1300.45);
person = &driver;
person -> PrintInfo();
person -> DisplaySalary(12,1700.78);
system("pause");
}
運行結果爲:
在這個程序中CPerson類中的虛函數DisplaySalary(int m,double s)僅起到爲派生類提供一個接口的作用,而派生類中重新定義的DisplaySalary(int m, double s)用於取決什麼樣的方式來計算工資。由於在CPerson中不能對此作出決定,因此被說明爲純虛函數。工人,教師和司機 等都視爲人類的對象,多態性保證了函數在對不同人羣計算工資時,無需關心當前正在計算那類人。在需要時函數可以從這些不同人羣的子類中獲得該對象的工資。