• 正文
  • 相關(guān)推薦
申請(qǐng)入駐 產(chǎn)業(yè)圖譜

適合具備 C 語(yǔ)言基礎(chǔ)的 C++ 教程(九)

2021/02/24
105
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

前言

在上一則教程中,敘述了關(guān)于C++類(lèi)型轉(zhuǎn)換的相關(guān)內(nèi)容,在本節(jié)教程中,將敘述 C++的另一個(gè)內(nèi)容,也就是抽象,這也是 C++相對(duì)于 C語(yǔ)言來(lái)說(shuō)獨(dú)特的一點(diǎn),下面我們就來(lái)著重?cái)⑹鲞@一點(diǎn)。

 

純虛函數(shù)

在介紹抽象類(lèi)之前,需要弄明白何為純虛函數(shù),下面假定我們有這樣一個(gè)需求:

做一個(gè)“各個(gè)國(guó)家的人的調(diào)查”,調(diào)查各個(gè)國(guó)家的人的:飲食、穿衣、開(kāi)車(chē)

要完成這樣一個(gè)事情,那我們現(xiàn)在就需要實(shí)現(xiàn)這樣幾個(gè)類(lèi),一個(gè)是 Human類(lèi),其他國(guó)家的人從 Human類(lèi)里派生而來(lái),就比如說(shuō)是ChineseEnglishman,我們?cè)倩剡^(guò)頭來(lái)想,我們所要實(shí)現(xiàn)的需求是調(diào)查各個(gè)國(guó)家的人,那么這個(gè)過(guò)程中,由Human類(lèi)派生得到 Chinese和 Englishman,那么在實(shí)例化對(duì)象的時(shí)候,我們實(shí)際上是不會(huì)用到Human類(lèi)去定義一個(gè)對(duì)象的,考慮到這層因素,我們?cè)?nbsp;Human類(lèi)里使用到了純虛函數(shù)的概念,類(lèi)實(shí)現(xiàn)的代碼如下所示:

class Human
{
private:
    int a;
public:
    /*純虛函數(shù)*/
    virtual void eating(void) = 0;
    virtual void wearing(void) = 0;
    virtual void driving(void) = 0;
};

class Englishman : public Human
{
public:
    void eating(void)  { cout<<"use knife to eat"<    void wearing(void) {cout<<"wear english style"<    void driving(void) {cout<<"drive english car"<};

class Chinese : public Human 
{
public:
    void eating(void)  { cout<<"use chopsticks to eat"<    void wearing(void) {cout<<"wear chinese style"<    void driving(void) {cout<<"drive chinese car"<};

我們可以看到在上述的代碼中,Human類(lèi)的成員函數(shù)跟前幾講所寫(xiě)的成員函數(shù)有所不同,而如上述 Human類(lèi)的成員函數(shù)這般寫(xiě)法,也就被稱(chēng)之為是純虛函數(shù)。

 

抽象類(lèi)

上述引出了純虛函數(shù)的寫(xiě)法,那純虛函數(shù)和抽象類(lèi)之間有什么關(guān)系呢?實(shí)際上,抽象類(lèi)就是具有純虛函數(shù)的類(lèi),那這抽象類(lèi)存在的意義是什么呢?總的來(lái)說(shuō),其作用也就是:向下定義好框架,向上提供統(tǒng)一的接口,其不能夠?qū)嵗瘜?duì)象,基于上述幾個(gè)類(lèi)的前提下,我們編寫(xiě)主函數(shù)的代碼:

int main(int argc,char **argv)
{
    Human h;

    return 0;
}

因?yàn)槌橄箢?lèi)不能夠?qū)嵗瘜?duì)象,所以上述代碼編譯結(jié)果是錯(cuò)誤的,錯(cuò)誤信息如下所示:

而使用通過(guò)抽象類(lèi)派生得到的派生類(lèi)實(shí)例化對(duì)象是可行的,代碼如下所示:

int main(int argc, char** argv)
{
    Englishman e;
    Chinese    g;

    return 0;
}

另外需要注意的是:在派生抽象類(lèi)的過(guò)程中,如果派生得到的子類(lèi)沒(méi)有覆寫(xiě)所有的純虛函數(shù),那么這個(gè)子類(lèi)還是抽象類(lèi),比如有如下所示的代碼,Human類(lèi)沿用的是上述的寫(xiě)法,代碼不變,如果我們將上述的 Chinese類(lèi)進(jìn)行更改,更改后的代碼如下所示:

class Chinese : public Human 
{
public:
    void eating(void) { cout<<"use chopsticks to eat"<    void wearing(void) {cout<<"wear chinese style"<    //void driving(void) {cout<<"drive chinese car"<};

如上述代碼所示,我們將 driving()函數(shù)注釋掉了,那么也就是說(shuō),我們并沒(méi)有將抽象類(lèi)的全部純虛函數(shù)進(jìn)行覆寫(xiě),那么當(dāng)前這個(gè)Chinese類(lèi)也是一個(gè)抽象類(lèi),也是不能夠進(jìn)行實(shí)例化對(duì)象的,要使得 Chinese類(lèi)有作用,我們必須派生出來(lái)另一個(gè)類(lèi),代碼如下所示:

class Guangximan : public Chinese 
{
    void driving(void) {cout<<"drive guangxi car"<};

這個(gè)時(shí)候,就可以用 Guangximan這個(gè)類(lèi)來(lái)實(shí)例化對(duì)象了。

多文件編程

在前面的教程中,有一則教程說(shuō)到了多文件編程,在 C++中也就是將類(lèi)的聲明放到頭文件中,將類(lèi)的實(shí)現(xiàn)放在.cpp文件中,為了更好地闡述這種方法,我們用實(shí)例來(lái)進(jìn)行講解,首先,來(lái)看一下,所涉及到地所有文件有哪些:

image-20210222103409774

 

可以看到上述有6個(gè)文件,我們首先來(lái)看 Chinese.h這個(gè)文件,代碼如下所示:

#ifndef _CHINESE_H
#define _CHINESE_H

#include 
#include 
#include 

using namespace std;

class Chinese
{
public:
    void eating(void); 
    void wearing(void);
    void drivering(void);
};

#endif

通過(guò)上述地.h文件可以看出,在這里的Chinese類(lèi)中,它只涉及到類(lèi)成員函數(shù)的一個(gè)聲明,并沒(méi)有成員函數(shù)的實(shí)現(xiàn),我們繼續(xù)來(lái)看Chinese.cpp的類(lèi)實(shí)現(xiàn):

#include "Chinese.h"

void Chinese::eating(void)
{
    cout << "use chopsticks to eat" << endl;
}

void Chinese::wearing(void)
{
    cout << "wear chinese style" << endl;
}

void Chinese::drivering(void)
{
    cout << "driver china car" << endl;
}

按照上述這樣一種方法,我們繼續(xù)來(lái)實(shí)現(xiàn)Englishman類(lèi)中的代碼,首先是Englishman.h中的代碼,代碼如下所示:

#ifndef _ENGLISHMAN_H
#define _ENGLISHMAN_H

#include 
#include 
#include 

using namespace std;

class Englishman
{
public:
    void eating(void);
    void wearing(void);
    void driver(void);
};

#endif

繼續(xù)看.cpp中的代碼,代碼如下所示:

#include "Englishman.h"

void Englishman::eating(void)
{
    cout << "use chopsticks to eat" << endl;
}

void Englishman::wearing(void)
{
    cout << "wear chinese style" << endl;
}

void Englishman::drivering(void)
{
    cout << "driver china car" << endl;
}

至此,除了主函數(shù)以外的代碼就編寫(xiě)完了,我們繼續(xù)來(lái)看主函數(shù)的代碼:

#include "Englishman.h"
#include "Chinese.h"

int main(int argc, char **argv)
{
    Englishman e;
    Chinese c;

    e.eating();
    c.eating();

    return 0;
}

在前面的教程中,我們就說(shuō)過(guò),如果是多文件的話,需要編寫(xiě) Makefile文件,Makefile文件代碼如下:

Human: main.o Chinese.o Englishman.o Human.o
    g++ -o $@ $^

%.o : %.cpp
    g++ -c -o $@ $<

clean:
    rm -f *.o Human 

上述代碼就不再這里贅述了,跟之前教程中的 Makefile基本是一樣的,有了Makefile之后,編譯代碼只需要使用 make命令就行了,編譯結(jié)果如下所示:

image-20210222105051169

 

上述代碼中,如果我們想要增添功能,比如說(shuō)ChineseEnglishman都有名字,那么就可以增添設(shè)置名字和獲取名字這兩種方法,首先是 Chinese的代碼,代碼如下:

#ifndef _CHINESE_H
#define _CHINESE_H

#include 
#include 
#include 

using namespace std;

class Chinese{
private:
    char *name;
public:
    void setName(char *name);
    char *getName(void);
    void eating(void);
    void wearing(void);
    void driving(void);
    ~Chinese();
};

#endif

然后是.cpp中的代碼:

#include "Chinese.h"

void Chinese::setName(char *name) 
{
    this->name = name;
}

char *Chinese::getName(void) 
{
    return this->name;
}

/*其他成員函數(shù)實(shí)現(xiàn)同上,這里省略*/

寫(xiě)完了 Chinese的代碼,然后是Englishman中的代碼,首先是Englishman.h中的代碼:

#ifndef _ENGLISHMAN_H
#define _ENGLISHMAN_H

#include 
#include 
#include 

using namespace std;

class Englishman {
private:
    char *name;
public:
    void setName(char *name);
    char *getName(void);
    void eating(void);
    void wearing(void);
    void driving(void);
    ~Englishman();
};

#endif

緊接著,是.cpp中的代碼:

#include "Englishman.h"

void Englishman::setName(char *name) 
{
    this->name = name;
}

char *Englishman::getName(void) 
{
    return this->name;
}

以這樣的方式增添功能,確實(shí)是可行的,但是我們假設(shè)一下,如果類(lèi)很多,除了中國(guó)人和英國(guó)人還有很多個(gè)國(guó)家的人,如果這些類(lèi)都要增加相同的功能,這個(gè)工作量就比較大了,那要如何解決這個(gè)問(wèn)題呢?這個(gè)時(shí)候,我們就可以引入一個(gè)新類(lèi)Human,然后,將每個(gè)類(lèi)相同的部分寫(xiě)在這個(gè)類(lèi)里面,其他類(lèi),諸如EnglisnmanChinese就可以從Human類(lèi)中繼承而來(lái),那這個(gè)時(shí)候,增添的操作,就只需要在 Human類(lèi)中增加就好了,不需要改動(dòng)ChineseEnglishman,工作量就小了很多。我們來(lái)看 Human類(lèi)的代碼實(shí)現(xiàn),首先是.h代碼的實(shí)現(xiàn):

#ifndef _HUMAN_H
#define _HUMAN_H

#include 
#include 
#include 

using namespace std;

class Human {
private:
    char *name;

public:
    void setName(char *name);
    char *getName(void);    
};

#endif

然后是.cpp代碼的實(shí)現(xiàn):

#include "Human.h"

void Human::setName(char *name) 
{
    this->name = name;
}

char *Human::getName(void) 
{
    return this->name;
}

有了 Human類(lèi)之后,我們就可以來(lái)實(shí)現(xiàn)我們所說(shuō)的 EnglishmanChinese類(lèi)了,代碼如下所示:

#ifndef _ENGLISHMAN_H
#define _ENGLISHMAN_H

#include 
#include 
#include 

#include "Human.h"

using namespace std;

class Englishman : public Human 
{
public:
    void eating(void);
    void wearing(void);
    void driving(void);
    ~Englishman();
};

#endif

然后是Chinese的代碼:

#ifndef _CHINESE_H
#define _CHINESE_H

#include 
#include 
#include 

#include "Human.h"

using namespace std;

class Chinese : public Human
{
public:
    void eating(void);
    void wearing(void);
    void driving(void);
    ~Chinese();
};

#endif

可以看到 EnglishmanChinese都是繼承自Human類(lèi),這個(gè)時(shí)候,就不需要再自己實(shí)現(xiàn)setNamegetName了。

我們繼續(xù)來(lái)完善我們的代碼,先從主函數(shù)說(shuō)起,主函數(shù)代碼如下所示:

void test_eating(Human *h)
{
    h->eating();
}

int main(int argc, char **argv)
{
    Englishman e;
    Chinese c;

    Human * h[2] = {&e,&h};
    int i;
    for (i = 0; i < 2; i++)
        test_eating(h[i]);

    return 0;
}

簡(jiǎn)要說(shuō)明一下主函數(shù)代碼的意思,其實(shí)就是定義了一個(gè)指針數(shù)組,然后遍歷整個(gè)指針數(shù)組,一次將數(shù)組內(nèi)的成員傳入test_eating()函數(shù)內(nèi),根據(jù)傳入的參數(shù)不同執(zhí)行不同的eating函數(shù),說(shuō)到這里,實(shí)際上是跟前面一則教程中所將的抽象類(lèi)和虛函數(shù)概念所結(jié)合起來(lái)的,因此,這里也是采用相同的思路,將 Human類(lèi)設(shè)置為抽象類(lèi),然后其他類(lèi)由Human類(lèi)派生而來(lái),下面就來(lái)看Human類(lèi)的代碼:

#ifndef _HUMAN_H
#define _HUMAN_H

#include 
#include 
#include 

using namespace std;

class Human {
private:
    char *name;

public:
    void setName(char *name);
    char *getName(void);
    virtual void eating(void) = 0;
    virtual void wearing(void) = 0;
    virtual void driving(void) = 0; 
};

#endif

然后是Human.cpp的代碼:

#include "Human.h"

void Human::setName(char *name) 
{
    this->name = name;
}

char *Human::getName(void) 
{
    return this->name;
}

實(shí)現(xiàn)了 Human類(lèi)的代碼之后,我們來(lái)看ChineseEnglishman的代碼,代碼如下所示,首先是 Englishman.h

#ifndef _ENGLISHMAN_H
#define _ENGLISHMAN_H

#include 
#include 
#include 

#include "Human.h"

using namespace std;

class Englishman : public Human 
{
public:
    void eating(void);
    void wearing(void);
    void driving(void);
};

#endif

緊接著是 Englishman.cpp的代碼:

#include "Englishman.h"

void Englishman::eating(void) 
{ 
    cout<<"use knife to eat"<}

void Englishman::wearing(void) 
{
    cout<<"wear english style"<}

void Englishman::driving(void) 
{
    cout<<"drive english car"<}

Chinese的代碼就不展示了,和Englishman是一個(gè)道理,總的來(lái)說(shuō),上述實(shí)際上也就是本節(jié)教程中抽象類(lèi)的一個(gè)多文件的實(shí)現(xiàn)。

動(dòng)態(tài)鏈接庫(kù)

回顧上述的代碼中的 Makefile文件,代碼如下所示:

Human: main.o Chinese.o Englishman.o Human.o
    g++ -o $@ $^

%.o : %.cpp
    g++ -c -o $@ $<

clean:
    rm -f *.o Human 

通過(guò)第一行代碼,我們知道只要更改main.c,Chinese.cEnglishman.c以及Human.o中的任意一個(gè)文件,都會(huì)導(dǎo)致重新生成一個(gè) Human文件,考慮到這一點(diǎn),實(shí)際上我們可以將 Chinese.o、Englishman.oHuman.o做成一個(gè)動(dòng)態(tài)庫(kù),至于這么做的原因是因?yàn)槲覀冊(cè)陂_(kāi)發(fā)一個(gè)大的項(xiàng)目的時(shí)候,會(huì)涉及到一個(gè)程序由多個(gè)人編寫(xiě),基本會(huì)分為兩類(lèi),一個(gè)是應(yīng)用編程,一個(gè)是類(lèi)編程,那么這兩者的區(qū)別如下所示:

應(yīng)用編程:使用類(lèi)

類(lèi)編程:提供類(lèi)編程,比如說(shuō) Englishman,Chinese

基于此,我們將之前的程序更改為這種形式,分為應(yīng)用編程和類(lèi)編程,基于上述我們對(duì)應(yīng)用編程和類(lèi)編程的區(qū)別的闡述,我們可以知道在剛剛那個(gè)程序,main.c就是應(yīng)用編程,而Englishman.cChinese.cHuman.c就是類(lèi)編程,而我們只需要更改 Makefile就可以實(shí)現(xiàn)這樣一個(gè)功能,更改之后的Makefile如下所示:

Human: main.o libHuman.so
    g++ -o $@ $< -L./ -lHuman

%.o : %.cpp
    g++ -fPIC -c -o $@ $<

libHuman.so : Englishman.o Chinese.o Human.o
    g++ -shared -o $@ $^

clean:
    rm -f *.o Human 

對(duì)比于之前的Makefile,我們可以看出第一行,Chinese.o、Englishman.oHuman.o替換成了現(xiàn)在的 libHuman.so,也就是說(shuō)現(xiàn)在的 Human文件生成依賴(lài)于main.olibHuman.so這兩個(gè)文件。第二行中的-L是表示,編譯的時(shí)候,指定搜索庫(kù)的路徑,而整個(gè)路徑就是緊隨其后的./,表示的是當(dāng)前文件夾下。而后面的-lHuman表示的是當(dāng)前鏈接的是Human整個(gè)庫(kù),要完全理解這里,還需要了解下Linux下的.so文件。

.so文件,被稱(chēng)之為共享庫(kù),是share object,用于動(dòng)態(tài)鏈接,說(shuō)到這里,可能會(huì)有所疑惑,明明寫(xiě)的是libHuman.so,為何在后面鏈接的時(shí)候?qū)懙氖?code>-lHuman,并不是-llibHuman呢,這就要了解一下.so文件的命名規(guī)則,.so文件是按照如下命名方式進(jìn)行命名的:lib+函數(shù)庫(kù)名+.so+版本號(hào)信息,也就是說(shuō)雖然寫(xiě)的是libHuman.so,但是實(shí)際生成的共享庫(kù)為Human,也就是為什么后面是-lHuman了。

繼續(xù)來(lái)看Makefile代碼,可以看到第四行也與之前的代碼不相同,多了一個(gè) -fPIC,這個(gè)參數(shù)的作用是:生成位置無(wú)關(guān)目標(biāo)碼,用于生成動(dòng)態(tài)鏈接庫(kù)。

繼續(xù)來(lái)看第八行,其中用到了一個(gè)之前未曾用過(guò)的-shared命令,這個(gè)命令的作用是:此選項(xiàng)將盡量使用動(dòng)態(tài)庫(kù),為默認(rèn)選項(xiàng)。優(yōu)點(diǎn):生成文件比較小。缺點(diǎn):運(yùn)行時(shí)需要系統(tǒng)提供動(dòng)態(tài)庫(kù)。

至此,Makefile代碼就完了,那么更改了的代碼與之前存在什么區(qū)別呢,我們先來(lái)回顧一下之前代碼的主函數(shù):

#include "Human.h"
#include "Englishman.h"
#include "Chinese.h"

void test_eating(Human *h)
{
    h->eating();
}


int main(int argc, char **argv)
{
    Englishman e;
    Chinese c;

    Human* h[2] = {&e, &c};
    int i;
    for (i = 0; i < 2; i++)
        test_eating(h[i]);

    return 0;
}

然后,我們進(jìn)行編譯,運(yùn)行:

image-20210223190725028

 

在上述中,我們看到編譯我們是用make命令進(jìn)行編譯的,然后在運(yùn)行可執(zhí)行代碼的時(shí)候,我們采用的是LD_LIBRARY_PATH=./ ./Human,與前面的教程不同,這次在運(yùn)行可執(zhí)行文件的時(shí)候,多了LD_LIBRARY_PATH=./,這是因?yàn)楝F(xiàn)在使用了動(dòng)態(tài)庫(kù),而這條多出來(lái)的語(yǔ)句是來(lái)指明動(dòng)態(tài)庫(kù)的路徑的。

最后,我們來(lái)測(cè)試一下,我們使用動(dòng)態(tài)鏈接庫(kù)所帶來(lái)的優(yōu)點(diǎn),比如,我現(xiàn)在更改了Chinese.cppeating函數(shù),代碼如下:

void Chinese::eating(void) 
{ 
    cout<<"use chopsticks to eat,test"<}

然后,如果沒(méi)有使用動(dòng)態(tài)鏈接庫(kù),那么這個(gè)時(shí)候,如果要執(zhí)行這個(gè)修改過(guò)的代碼,就需要重新生成可執(zhí)行文件,但是現(xiàn)在使用了動(dòng)態(tài)鏈接庫(kù),也就是說(shuō),不需要重新生成可執(zhí)行文件了,我們只需要重新生成動(dòng)態(tài)鏈接庫(kù)就好了,編譯命令如下所示:

image-20210223191802201

 

可見(jiàn),上述只重新生成了Human庫(kù)文件,并沒(méi)有重新生成可執(zhí)行文件,代碼運(yùn)行正確,這樣也就做到了應(yīng)用編程和類(lèi)編程分離。

小結(jié)

上述便是本期教程的所有內(nèi)容,教程所涉及的代碼可以通過(guò)百度云鏈接的方式獲取。

鏈接:https://pan.baidu.com/s/1fB78jG6PdMNcXMfPwtqyvw

提取碼:cquv

相關(guān)推薦

登錄即可解鎖
  • 海量技術(shù)文章
  • 設(shè)計(jì)資源下載
  • 產(chǎn)業(yè)鏈客戶(hù)資源
  • 寫(xiě)文章/發(fā)需求
立即登錄

在讀碩士研究生,喜歡鉆研嵌入式相關(guān)技術(shù),熱衷于寫(xiě)文章分享知識(shí),不定期輸出關(guān)于單片機(jī), RTOS,信號(hào)處理等相關(guān)內(nèi)容