1. 程式人生 > >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++開發的時候,基類的解構函式一般都是虛擬函式。