程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++純虛類小覽

C++純虛類小覽

編輯:C++入門知識

C++純虛類小覽


一、文章來由

virtual 方法和 virtual 類可以說是c++語言的一大特性,甚至有人說是c++語言的精髓,其實這麼說也是有一定道理的,因為運行時多態在c++中體現淋漓盡致,而 virtual 就是為多態服務的。這也是一個一定要搞懂的c++問題,所以有了這篇文章。同時,我覺得這類底層問題不可能一文以蔽之,而且我也相信真正想搞懂這個問題的讀者,不會只讀我這一篇文章,所以只是小覽,同時歡迎討論和指正。

二、引入原因

其實,引入純虛函數的原因我在我另一篇文章虛函數與多態小覽就有寫,不過重要的話說三遍,還只是兩遍呢,哈哈~~知其然也需要知其所以然

2.1 定義

純虛函數:純虛函數是在基類中聲明的虛函數,它在基類中沒有定義,但要求任何派生類都要定義自己的實現方法。在基類中實現純虛函數的方法是在函數原型後加“=0”。

純虛類:含有一個或以上純虛函數的類,叫做純虛類,又叫抽象類。

如: virtual void funtion()=0

2.2 引入原因

1、為了方便使用多態特性,我們常常需要在基類中定義虛擬函數。

2、在很多情況下,基類本身生成對象是不合情理的。

例如,動物作為一個基類可以派生出老虎、孔雀等子類,但動物本身生成對象明顯不合常理。為了解決上述問題,引入了純虛函數的概念,將函數定義為純虛函數(方法:virtual ReturnType Function()= 0;),則編譯器要求在派生類中必須予以重寫以實現多態性。同時含有純虛擬函數的類稱為抽象類,它不能生成對象。這樣就很好地解決了上述兩個問題。

這樣就清楚有純虛類的原因了吧~~

三、純虛類的特性

3.1 直接上特性

羅列的不一定有順序,但是盡量羅列到,歡迎補充

1、首先說明:純虛類是區別於虛類的。虛類可以實例化,但是純虛類不能被實例化,也就永遠沒有對象。。。。。。T.T(永遠沒有對象,好慘!!)

2、如果子類沒有實現純虛函數,相當子類也有純虛函數,所以子類也是純虛類。

3、其他類的定義與使用方式都與一般類差不多,大致有如下地方:

1)純虛類可以有成員變量 (可以)
2)純虛類可以有普通的成員函數 (可以)
3)純虛類可以有其他虛函數 (可以)
4)純虛類可不可以有帶有參數的構造函數?(可以)
5)可不可以在純虛類的派生類的構造函數中顯式調用純虛類的帶參數構造函數 (可以)

3.2、一篇文章對純虛類的描述

一篇文章中這樣寫道:

1、抽象類是一種特殊的類,它是為了抽象和設計的目的而建立的,它處於繼承層次結構的較上層(而不是絕對的上層,也有可能是中層,甚至底層)。

2、在實際中為了強調一個類是抽象類,可將該類的構造函數(設置為protected) 說明為保護的訪問控制權限。

文章還寫道:

3、抽象類的主要作用是將有關的組織在一個繼承層次結構中,由它來為它們提供一個公共的根 (其實不一定是根),相關的子類是從這個根派生出來的。

4、抽象類刻畫了一組子類的操作接口的通用語義,這些語義也傳給子類。一般而言,抽象類只描述這組子類共同的操作接口,而完整的實現留給子類。

5、抽象類只能作為基類來使用(大多數情況是其他類的基類,但是抽象類本身也有可能是子類)。

6、可以定義指向抽象類的指針和引用,此指針可以指向它的派生類,進而實現多態性。

四、純虛類例子

作為接口使用的例子:

#include
#include
#include
#include
using namespace std;

class animal
{
protected:
    animal(){}
    virtual void  eat(const string name) =0;
};


class dog:public animal
{
public:
    vector m_food;

    void eat(const string name);
    void push_back(const string name);
};

void dog::eat(const string name)
{
    vector::iterator iter=std::find(m_food.begin(),m_food.end(),name);
    if (m_food.end() !=iter)
    {
        cout<

另一個好例子,多態的實例:

#include
using namespace std;

const double PI=3.14159;

class Shapes   //抽象類
{
protected:
    int x, y;
public:
    void setvalue(int d, int w=0){x=d;y=w;}
    virtual void disp()=0;//純虛函數
};

class Square:public Shapes
{
public:
    void disp(){
        cout<<矩形面積:<setvalue(10, 5);
    ptr[0]->disp();
    ptr[1] = &c1;
    ptr[1]->setvalue(10);
    ptr[1]->disp();
    return 0;

}

五、純虛類與虛函數表

有這麼個問題:

我們知道C++中有虛函數的類會有一個對應的虛函數表,那麼純虛類有虛表嗎,如果有的話怎麼調用純虛函數?

直覺上來講,應該是有的。可是既然是純虛類,說明其對象永遠不會被創建,那麼維護個虛表貌似也不是很必要了。

設計個程序來驗證一下:

class VirtualBase
{
public:
    VirtualBase()
    {
        // call pure virtual function through a non virtual function in base class's constructor
        non_virtual_fun();
    }

    void non_virtual_fun()
    {
        virtual_fun();
    }

    virtual void virtual_fun() = 0;
};

class Derived: public VirtualBase
{
public:
    virtual void virtual_fun(){}
};

int main(int argc, const char* argv[])
{

    size_t size = sizeof(VirtualBase);

    Derived d;
}

分析:

(1)sizeof(VirtualBase)返回4,而不是代表空類的1,這說明其內部是有一個虛表指針,而有虛表指針,那麼極有可能有一個虛表。

(2)通過上面的程序,我們在實例化Derived時可以調入VirtualBase的構造函數,此時可以在watch窗口中查看this指針,的確有一個虛表指針指向一個虛表。

這裡寫圖片描述

在構造函數中通過一個非虛函數調用一個純虛函數 - 雖然該純虛函數在Derived中被實現,但此時還在基類的構造函數中,所以,純虛函數被調用 - 出錯

這裡寫圖片描述

純虛類需要虛表,我能想到的一個原因是在多個派生類中override時,我們需要保證被改寫的函數是在正確的偏移地址的,為了保證這個地址是正確的,事先准備一個模板還是比較重要的。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved