C++學習筆記 (六) ---- C++多型與虛擬函式
①、多型的概念
先上一個示例
#include <iostream> using namespace std; //基類People class People{ public: People(char *name, int age); void display(); protected: char *m_name; int m_age; }; People::People(char *name, int age): m_name(name), m_age(age){} void People::display(){ cout<<m_name<<m_age<<"歲了."<<endl; } //派生類Student class Student: public People{ public: Student(char *name, int age, int salary); void display(); private: int m_score; }; Student::Student(char *name, int age, int score): People(name, age), m_score(score){} void Student::display(){ cout<<m_name<<m_age<<"歲了,是一名學生,成績:"<<m_score<<endl; } int main(){ People *p = new People("李明", 16); p -> display(); p = new Student("曉紅", 15, 95); p -> display(); return 0; } //執行結果 李明16歲了. 曉紅15歲了.
如上,當基類指標 p 指向派生類 Student 的物件時,雖然使用的是 Student 的成員變數,但是卻沒有使用它的成員函式。所以為了避免這種錯誤的發生,引入了虛擬函式(virtual)。
使用時在函式前增加 virtual 關鍵字就行。上個示例中在基類的成員函式 display() 的宣告前加上 virtual 即可。
因為有了虛擬函式,所以基類指標在指向基類的時候就執行基類的操作函式,指向派生類的時候就使用派生類的成員函式,即 p->display() 這一條語句實現不同的操作,這種現象就叫做多型。虛擬函式的唯一作用就是構成多型。
②、引用也可以實現多型
引用的本質是通過指標實現的,所以引用也可以實現多型。
將上述例子中的 main() 函式內部改為引用的形式,結果也是一樣
int main(){
People p("李明", 16);
Teacher t("曉紅", 15, 95);
People &rp = p;
People &rt = t;
rp.display();
rt.display();
return 0;
}
多型的用途:通過基類指標對所有派生類(直接或間接)的成員變數和成員函式進行訪問,要是沒有多型,只能訪問成員變數。在派生類比較多的情況下,要是不使用多型,就需要定義多個指標變數,很容易造成混亂;有了多型,就只需要一個指標就可以呼叫所有派生類中的虛擬函式。
③、虛擬函式
只需在基類的虛擬函式宣告處加上 virtual 關鍵字即可;當在基類中定義了虛擬函式時,要是派生類沒有定義新的函式來遮蔽此函式,那麼將使用基類的虛擬函式;只有派生類的虛擬函式遮蔽基類的虛擬函式才能形成多型。
如:基類虛擬函式為 virtual void fun();,派生類虛擬函式為 virtual void fun(int);,那麼基類指標指向派生類物件的時候,p->fun(100);將會出錯,而語句 p->fun(); 將呼叫基類的函式。
建構函式不能是虛擬函式,解構函式可以宣告為虛擬函式。
示例:
#include <iostream>
using namespace std;
//基類Base
class Base{
public:
virtual void func();
virtual void func(int);
};
void Base::func(){
cout<<"void Base::func()"<<endl;
}
void Base::func(int n){
cout<<"void Base::func(int)"<<endl;
}
//派生類Derived
class Derived: public Base{
public:
void func();
void func(char *);
};
void Derived::func(){
cout<<"void Derived::func()"<<endl;
}
void Derived::func(char *str){
cout<<"void Derived::func(char *)"<<endl;
}
int main(){
Base *p = new Derived();
p -> func(); //輸出void Derived::func()
p -> func(10); //輸出void Base::func(int)
p -> func("hello C++"); //compile error
return 0;
}
語句 p -> func(); 呼叫的是派生類的虛擬函式,構成了多型。
語句 p -> func(10); 呼叫的是基類的虛擬函式,因為派生類中沒有函式遮蔽它。
語句 p -> func("hello C++"); 出現編譯錯誤,因為基類的指標只能訪問從基類繼承過去的成員,不能訪問派生類新增的成員。
④、虛解構函式的重要性
虛解構函式主要是為了避免記憶體洩漏,只有當派生類中有指標成員變數時才會使用。因此虛解構函式的作用是在刪除指向派生類物件的基類指標時,可以呼叫派生類的解構函式來釋放派生類中的堆記憶體。
具體地說,如果派生類中申請了記憶體空間,並在其解構函式中對這些記憶體空間進行釋放。假設基類中採用的是非虛解構函式,當刪除基類指標指向的派生類物件時就不會觸發動態繫結,因而只會呼叫基類的解構函式,而不會呼叫派生類的解構函式。那麼在這種情況下,派生類中申請的空間就得不到釋放從而產生記憶體洩漏。所以,為了防止這種情況的發生,C++中基類的解構函式應採用 virtual 虛解構函式。
C++開發的時候,基類的解構函式一般都是虛擬函式。