多態(tài)的C++實現

多態(tài)的C++實現

1 多態(tài)的原理

什么是多態(tài)?多態(tài)是面向對象的特性之一,只用父類指針指向子類的對象。

1.1 多態(tài)實現的三個條件

  • 存在繼承
  • 虛函數重寫
  • 父類指針指向子類對象

如下代碼,如果并沒有增加virtual關鍵字,并不會發(fā)生多態(tài)現象。


#include <iostream>
using namespace std;

class Parent {
    
    
public:
    
    Parent(int a, int b)
    {
        this->p1 = a;
        this->p2 = b;
    }
    
    //多態(tài)的重點:需要加virtual關鍵字,否則不會發(fā)生多態(tài)
    virtual void add()
    {
        cout<<"Parent: p1 + p2 "<<endl;
    }
    
private:
    int p1;
    int p2;
    
};

class Child :public Parent {
    
    
public:
    
    Child(int a, int b, int c, int d):Parent(c,d)
    {
        this->c1 = a;
        this->c2 = b;
    }
    
    void add()
    {
        cout<<"Child: c1 + c2 "<<endl;
    }
    
private:
    int c1;
    int c2;
    
    
};


void play(Parent *base)
{
    base->add();
}


int main(int argc, const char * argv[]) {
    
    Child c(1,2,3,4);
    
    Parent *pP = &c;
    Child  *pC = &c;
    
    play(pP);
    play(pC);
    
    return 0;
}


1.2 多態(tài)的實現原理

上面的例子中,在基類的函數增加了virtual關鍵字后,編譯器會自動為子類對應的方法也會增加virtual關鍵字

1.2.1 虛函數表
  • 當類中聲明了虛函數時,編譯器會自動為類生成一張?zhí)摵瘮当怼?/li>
  • 虛函數表一個存儲類成員函數指針的數據結構
  • 虛函數表是有編譯器自動生成與維護的
1.2.2 vptr指針

如果存在virtual關鍵字,編譯器在運行時的時候(動態(tài)聯(lián)編)會自動為當前對象增加vptr指針,這個vptr指針指向了當前類的虛函數表。

判斷vptr指針是否存在

 //如果vptr指針存在,則對象sizeof()之后,大小會發(fā)生變化
 #include <iostream>
 using namespace std;

 class Parent {
    
    
 public:
    
    Parent(int a, int b)
    {
        this->p1 = a;
        this->p2 = b;
    }
    
    virtual void add()
    {
        cout<<"Parent: p1 + p2 "<<endl;
    }
    
 private:
    int p1;
    int p2;
    
 };
 
 int main(int argc, const char * argv[]) {
    
    Parent p(1,2);
    //可以分別測試下,添加virtual關鍵字和不加的內存空間大小
    cout<<sizeof(p)<<endl;
 }

1.2.2 多態(tài)實現原理
  1. 編譯器發(fā)現存在virtual關鍵字,則會為類生成一張?zhí)摵瘮当?/li>
  2. 編譯器會在動態(tài)聯(lián)編(運行時)時為對象添加一個vptr指針
  3. 有父類對象指向子類對象存在,且執(zhí)行了父類方法
  4. 如果當前對象有vptr指針存在,則會通過vptr指針找到對應的虛函數表,在虛函數表中查找對應的方法地址,執(zhí)行。

2 vptr指針的分步初始化

2.1 父類構造函數中調用父類的方法,會產生多態(tài)嗎?

如果再父類的構造函數中,調用父類的虛函數。那么在子類對象初始化的時候,會不會產生多態(tài)現象呢,還是仍然調用父類的虛函數呢?

答案是:否。不會產生多態(tài)。因為vptr指針是分步初始化的

#include <iostream>
using namespace std;

class Parent {
    
    
public:
    
    Parent(int a, int b)
    {
        this->p1 = a;
        this->p2 = b;
        
        this->add();    //調用父類的虛函數,這個地方不會產生多態(tài)現象。
    }
    
    virtual void add()
    {
        cout<<"Parent: p1 + p2 "<<endl;
    }
    
private:
    int p1;
    int p2;
    
};

class Child :public Parent {
    
    
public:
    
    Child(int a, int b, int c, int d):Parent(c,d)
    {
        this->c1 = a;
        this->c2 = b;
    }
    
    virtual void add()
    {
        cout<<"Child: c1 + c2 "<<endl;
    }
    
private:
    int c1;
    int c2;
    
    
};


void play(Parent *base)
{
    base->add();
}


int main(int argc, const char * argv[]) {
    
    //雖然會調用父類的構造函數,但是仍然是調用父類的add方法
    Child c(1,2,3,4);
    
    
    return 0;
}

2.2 vptr指針的分步初始化

那么vptr指針是如何初始化的呢?

  1. 編譯器編譯時,基類會產生虛函數表,子類也會產生虛函數表
  2. 當初始化子類對象的時候,先調用父類的構造函數,同時將子類對象的vptr指針指向父類的虛函數表
  3. 接下來,調用自己的構造函數,同時將vptr指針指向子類的虛函數表

上面的例子中,當調用父類構造函數時,當前vptr指針仍然指向父類的虛函數表,調用的仍然是父類的add方法,不會產生多態(tài)。

3 多態(tài)帶來的問題

可以使用父類指針指向子類的對象,但是指針的類型卻改變了,最指針進行++或者--操作時,可能帶來意向不到的后果。

#include <iostream>
using namespace std;

class Parent {
    
    
public:
    
    Parent(int a, int b)
    {
        this->p1 = a;
        this->p2 = b;
    }
    
    virtual void print()
    {
        cout<<"p1 = "<<p1<<"; p2 = "<<p2<<endl;
    }

    
private:
    int p1;
    int p2;
    
};

class Child :public Parent {
    
    
public:
    
    Child(int a, int b, int c, int d):Parent(c,d)
    {
        this->c1 = a;
        this->c2 = b;
    }
    
    virtual void print()
    {
        cout<<"c1 = "<<c1<<"; c2 = "<<c2<<endl;
    }
    
private:
    int c1;
    int c2;
    
    
};


int main(int argc, const char * argv[]) {
    
    
    
    Child array[] = {Child(1,2,3,4),Child(5,6,7,8),Child(9,10,11,12),Child(13,14,15,17),Child(18,19,20,21)};
    
    Child *c = array;
    c->print();
    c++;
    c->print();
    
    Parent *p = array;
    p->print(); //仍然打印的是子類的(沒問題)
    p++;        //執(zhí)行++操作
    p->print(); //執(zhí)行之后,崩潰(因為p++和c++的步長不一樣導致錯誤)
    
    
    return 0;
}

4 純虛函數和虛基類(抽象類)

  • 純虛函數是一個在基類中說明的虛函數,在基類中沒有定義,要求任何派生類都定義自己的版本
  • 純虛函數為各派生類提供了一個公共界面(接口的封裝和設計、軟件的模塊功能劃分)
  • 純虛函數的聲明virtual void print() = 0;
  • 一個具有純虛函數的基類成為抽象類

注意:

  • 抽象類不能建立對象,但可以聲明抽象類的指針
  • 抽象類不能作為返回來興,也不能作為參數類型
  • 抽象類可以聲明抽象類的引用
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容