1. 程式人生 > >C++系列(純虛擬函式和抽象類)

C++系列(純虛擬函式和抽象類)

下面通過一個例子來說明純虛擬函式的定義方法

在這個類當中,我們定義了一個普通的虛擬函式,並且也定義了一個純虛擬函式。那麼,純虛擬函式是什麼呢??從上面的定義可以看到,純虛擬函式就是沒有函式體,同時在定義的時候,其函式名後面要加上“= 0”。

純虛擬函式的實現原理 本節從虛擬函式表的角度來說明純虛擬函式的實現原理。

上面就是我們在前面課程講到的多型的實現原理,在講這一部分的時候,講到了虛擬函式表以及虛擬函式表指標。如果我們定義了Shape這樣的類,那麼,Shape類當中,因為有虛擬函式和純虛擬函式,所以,它一定有一個虛擬函式表,當然,也就一定有一個虛擬函式表指標。如果是一個普通的虛擬函式,那麼,在虛擬函式表中,其函式指標就是一個有意義的值;如果是一個純虛擬函式,那麼,在虛擬函式表中,其函式指標的值就是0。也就是說,在虛擬函式表當中,如果是純虛擬函式,那麼就實實在在的寫上0,如果是普通的虛擬函式,那就肯定是一個有意義的值。通過對純虛擬函式的講解,大家也一定會發現:純虛擬函式也一定是某個類的成員函式,那麼,包含純虛擬函式的類也叫作什麼呢?我們把包含純虛擬函式的類稱之為抽象類。比如剛剛舉的Shape類當中就含有一個計算周長的純虛擬函式,那麼,我們就說這個Shape類是一個抽象類。大家可以想一想,如果我們使用Shape這個類去例項化一個物件,那麼這個物件例項化之後,如果想要去呼叫純虛擬函式(比如要去呼叫這個計算周長的純虛擬函式),那怎麼去呼叫呢???我們說,顯然是無法呼叫的。所以,我們得出一個結論:對於抽象類來說,C++是不允許它去例項化物件的。也就是說,抽象類無法例項化物件。那麼,如果我們強行寫成如下形式:

比如上面的,從棧中或者堆中去例項化一個物件,此時,如果我們去執行程式的話,計算機就會報錯。而且,不僅如此,對於抽象類的子類也可以是抽象類。比如:我們如果定義一個Person的類如下:

因為人是要工作的,所以定義了一個work()函式,同時還定義了一個列印資訊的函式。由於人比較抽象,所以也不知道工作要做啥,所以就定義work()為純虛擬函式,同時,也不知道該列印啥資訊,所以也定義成了純虛擬函式。當我們使用Worker這個類去繼承Person類的時候,我們可以想象一下,對於工人來說,其工種是非常多的,單單一個工人,我們倒是可以一些他的資訊(比如:這個工人的名字,工號等等),但是,這個工人是什麼工作,具體是做什麼的,我們也沒有辦法清晰明瞭的描述出來,所以這個時候,我們可以也把它定義成一個純虛擬函式,如下所示。此時,這個Worker類作為Person的子類來說,它也是一個抽象類。

當我們明確了這個工人是什麼工種(比如他是一名清潔工),清潔工這個類繼承了Worker類(清潔工也是工人的一種),那麼work()這個函式就有了一個明確的定義了(比如:他的工作就是掃地,我們可以將其打印出來),如下圖所示。那麼,此時,我們就可以使用清潔工(Dustman)這個類去例項化對

到此,我們需要強調說明一點的是:對於抽象類來說,它無法例項化物件,而對於抽象類的子類來說,只有把抽象類中的純虛擬函式全部實現之後,那麼這個子類才可以例項化物件。

純虛擬函式和抽象類的程式碼實踐 題目描述:

/* ************************************************ */

/* 純虛擬函式和抽象類

       1. Person類,成員函式:建構函式,虛解構函式,純虛擬函式work(),資料成員:名字 m_strName

       2. Worker類,成員函式:建構函式,work(),資料成員:年齡m_iAge

       3. Dustman類,成員函式:建構函式,work()

*/

/* ************************************************ */

程式框架:

首先,我們來驗證一下,含有純虛擬函式的類,即抽象類能否例項化物件

標頭檔案(Person.h)

#ifndef PERSON_H #define PERSON_H

#include <string> using namespace std; class Person { public:     Person(string name);     virtual void work() = 0; //定義函式work()為純虛擬函式     virtual ~Person() { } private:     string m_strName; };

#endif

源程式(Person.cpp)

#include "Person.h"

Person::Person(string name) {     m_strName = name; }

我們看到Person.h檔案中定義了一個純虛擬函式work(),即此時Person這個類是一個抽象類。接下來我們在main函式中去例項化一個Person類的物件看看情況如何?

主調程式(demo.cpp)

#include <stdlib.h>

#include "Person.h"

using namespace std;

int main() {     Person person("Zhangsan");

    system("pause");     return 0; }

此時,我們除錯一下程式(F7)看一看結果如何?執行結果如下:

我們可以看到錯誤提示:”Person”是一個抽象類,不能例項化。當我們雙擊這一行錯誤提示行的時候,箭頭就會指向程式程式碼中的main函式中的例項化語句。

接著,我們看一看抽象類的子類是否能夠例項化?

我們使用Worker這個類來繼承Person類,如下:

標頭檔案(Worker.h)

#ifndef WORKER_H #define WORKER_H

#include "Person.h" class Worker:public Person { public:     Worker(string name, int age); private:     int m_iAge; };

#endif

源程式(Worker.cpp)

#include <iostream> #include "Worker.h"

using namespace std;

Worker::Worker(string name, int age):Person(name) {     m_iAge = age; }

我們看到,在Worker這個類當中,我們並沒有對work()這個函式做特別的處理,而是從Person類中完全繼承下來了。由於Person類中的work()函式是一個純虛擬函式,那麼這就導致Worker這個類也變成了一個抽象類。那麼,此時我們在main函式中去例項化一個Worker類的物件的話,結果如何呢?

主調程式(demo.cpp)

#include <iostream> #include <stdlib.h>

#include "Person.h" #include "Worker.h"

using namespace std;

int main() {     Worker worker("ZhangSan", 30);

    system("pause");     return 0; }

此時,我們除錯一下程式(F7)看一看結果如何?執行結果如下:

我們可以看到錯誤提示:”Worker”是一個抽象類,不能例項化。當我們雙擊這一行錯誤提示行的時候,箭頭就會指向程式程式碼中的main函式中的例項化語句。

接下來,我們在Worker這個類中,對work()函式進行實現,然後再例項化一個Worker類物件,看一看結果又是如何?

標頭檔案(Worker.h)

#ifndef WORKER_H #define WORKER_H

#include "Person.h" class Worker:public Person { public:     Worker(string name, int age);     virtual void work(); private:     int m_iAge; };

#endif

我們看到對於Worker這個類來說,其中也有一個work()函式。我們來看一看Worker.cpp檔案

源程式(Worker.cpp)

#include <iostream> #include "Worker.h"

using namespace std;

Worker::Worker(string name, int age):Person(name) {     m_iAge = age; }

void Worker::work() {     cout << "work()" << endl; }

在這裡,我們看到,在Worker.cpp中對work()這個函式進行了實現,此時我們再來例項化Worker物件如下:

主調程式(demo.cpp)

#include <iostream> #include <stdlib.h>

#include "Person.h" #include "Worker.h"

using namespace std;

int main() {     Worker worker("ZhangSan", 30);

    system("pause");     return 0; }

此時,我們除錯一下程式(F7)看一看結果如何?執行結果如下:

從結果看到,此時計算機編譯通過。這樣,也就從另一個角度說明了,Worker這個類雖然繼承自抽象類Person類,但此時Worker類中已經沒有了純虛擬函式,但凡是虛擬函式,也已經被實現了。

從上面我們可以看到:如果Worker類中的work()函式也不進行實現,那麼Worker這個類仍然是一個抽象類,也就不能進行例項化,此時,就待依賴Worker的子類來實現純虛擬函式,從而就將此重任交給了清潔工(Dustman)這個類。那麼,往往這種情況是存在的,因為對於人類來說,勞動這個函式很抽象,不知道該如何勞動,而到了Worker這個類中,勞動仍然比較抽象,因為我們雖然知道其是一個工人,但工人的工種有很多,工種不同,其具體勞動也不是不一樣的,所以在Worker這個類當中,對work進行實現,往往顯得也不太合適。那麼,更多情況下,是在更具體的類中去實現work這個函式。比如說,在清潔工(Dustman)這個類中,工作就已經很明確了,他的工作就是掃地,所以對work進行實現的時候,就打印出“掃地”就行了,如下:

標頭檔案(Worker.h)

#ifndef WORKER_H #define WORKER_H

#include "Person.h" class Worker:public Person { public:     Worker(string name, int age); private:     int m_iAge; };

#endif

源程式(Worker.cpp)

#include <iostream> #include "Worker.h"

using namespace std;

Worker::Worker(string name, int age):Person(name) {     m_iAge = age; }

標頭檔案(Dustman.h)

#ifndef DUSTMAN_H #define DUSTMAN_H

#include "Worker.h"

class Dustman:public Worker { public:     Dustman(string name, int age);     virtual void work(); }; #endif

源程式(Dustman.cpp)

#include "Dustman.h" #include <iostream> using namespace std; Dustman::Dustman(string name, int age):Worker(name,age) {

}

void Dustman::work() {     cout << "掃地" << endl; }

主調程式(demo.cpp)

#include <stdlib.h> #include "Dustman.h" #include "Person.h" #include "Worker.h"

using namespace std;

int main() {     Dustman dustman("ZhangSan", 30);

    system("pause");     return 0; }

此時,我們除錯一下程式(F7)看一看結果如何?執行結果如下: