前言
在上一則教程中,敘述了關(guān)于
C
類型轉(zhuǎn)換的相關(guān)內(nèi)容,在本節(jié)教程中,將敘述?
C
的另一個(gè)內(nèi)容,也就是
抽象,這也是?
C
相對(duì)于?
C
語言來說獨(dú)特的一點(diǎn),下面我們就來著重?cái)⑹鲞@一點(diǎn)。
純虛函數(shù)
在介紹
抽象類之前,需要弄明白何為純虛函數(shù),下面假定我們有這樣一個(gè)需求:
做一個(gè)“各個(gè)國家的人的調(diào)查”,調(diào)查各個(gè)國家的人的:飲食、穿衣、開車
要完成這樣一個(gè)事情,那我們現(xiàn)在就需要實(shí)現(xiàn)這樣幾個(gè)類,一個(gè)是?
Human
類,其他國家的人從?
Human
類里派生而來,就比如說是
Chinese
和
Englishman
,我們再回過頭來想,我們所要實(shí)現(xiàn)的需求是調(diào)查
各個(gè)國家的人
,那么這個(gè)過程中,由
Human
類派生得到?
Chinese
和?
Englishman
,那么在實(shí)例化對(duì)象的時(shí)候,我們實(shí)際上是不會(huì)用到
Human
類去定義一個(gè)對(duì)象的,考慮到這層因素,我們在?
Human
類里使用到了純虛函數(shù)的概念,類實(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"<<endl;?}
????void?wearing(void)?{cout<<"wear?english?style"<<endl;?}
????void?driving(void)?{cout<<"drive?english?car"<<endl;?}
};
class?Chinese?:?public?Human?
{
public:
????void?eating(void)??{?cout<<"use?chopsticks?to?eat"<<endl;?}
????void?wearing(void)?{cout<<"wear?chinese?style"<<endl;?}
????void?driving(void)?{cout<<"drive?chinese?car"<<endl;?}
};
我們可以看到在上述的代碼中,
Human
類的成員函數(shù)跟前幾講所寫的成員函數(shù)有所不同,而如上述?
Human
類的成員函數(shù)這般寫法,也就被稱之為是
純虛函數(shù)。
抽象類
上述引出了純虛函數(shù)的寫法,那純虛函數(shù)和抽象類之間有什么關(guān)系呢?實(shí)際上,
抽象類就是具有純虛函數(shù)的類,那這抽象類存在的意義是什么呢?總的來說,其作用也就是:
向下定義好框架,向上提供統(tǒng)一的接口,其不能夠?qū)嵗瘜?duì)象,基于上述幾個(gè)類的前提下,我們編寫主函數(shù)的代碼:
int?main(int?argc,char?**argv)
{
????Human?h;
????return?0;
}
因?yàn)槌橄箢惒荒軌驅(qū)嵗瘜?duì)象,所以上述代碼編譯結(jié)果是錯(cuò)誤的,錯(cuò)誤信息如下所示:
而使用通過抽象類派生得到的派生類實(shí)例化對(duì)象是可行的,代碼如下所示:
int?main(int?argc,?char**?argv)
{
????Englishman?e;
????Chinese????g;
????return?0;
}
另外需要注意的是:在派生抽象類的過程中,如果派生得到的子類沒有覆寫所有的純虛函數(shù),那么這個(gè)子類還是抽象類,比如有如下所示的代碼,
Human
類沿用的是上述的寫法,代碼不變,如果我們將上述的?
Chinese
類進(jìn)行更改,更改后的代碼如下所示:
class?Chinese?:?public?Human?
{
public:
????void?eating(void)?{?cout<<"use?chopsticks?to?eat"<<endl;?}
????void?wearing(void)?{cout<<"wear?chinese?style"<<endl;?}
????//void?driving(void)?{cout<<"drive?chinese?car"<
};
如上述代碼所示,我們將?
driving()
函數(shù)注釋掉了,那么也就是說,我們并沒有將抽象類的全部純虛函數(shù)進(jìn)行覆寫,那么當(dāng)前這個(gè)
Chinese
類也是一個(gè)抽象類,也是不能夠進(jìn)行實(shí)例化對(duì)象的,要使得?
Chinese
類有作用,我們必須派生出來另一個(gè)類,代碼如下所示:
class?Guangximan?:?public?Chinese?
{
????void?driving(void)?{cout<<"drive?guangxi?car"<<endl;?}
};
這個(gè)時(shí)候,就可以用?
Guangximan
這個(gè)類來實(shí)例化對(duì)象了。
多文件編程
在前面的教程中,有一則教程說到了多文件編程,在?
C
中也就是將類的聲明放到頭文件中,將類的實(shí)現(xiàn)放在
.cpp
文件中,為了更好地闡述這種方法,我們用實(shí)例來進(jìn)行講解,首先,來看一下,所涉及到地所有文件有哪些:
可以看到上述有
6
個(gè)文件,我們首先來看?
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
通過上述地
.h
文件可以看出,在這里的
Chinese
類中,它只涉及到類成員函數(shù)的一個(gè)聲明,并沒有成員函數(shù)的實(shí)現(xiàn),我們繼續(xù)來看
Chinese.cpp
的類實(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ù)來實(shí)現(xiàn)
Englishman
類中的代碼,首先是
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ù)以外的代碼就編寫完了,我們繼續(xù)來看主函數(shù)的代碼:
#include?"Englishman.h"
#include?"Chinese.h"
int?main(int?argc,?char?**argv)
{
????Englishman?e;
????Chinese?c;
????e.eating();
????c.eating();
????return?0;
}
在前面的教程中,我們就說過,如果是多文件的話,需要編寫?
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é)果如下所示:
上述代碼中,如果我們想要增添功能,比如說
Chinese
和
Englishman
都有名字,那么就可以增添設(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)同上,這里省略*/
寫完了?
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è)一下,如果類很多,除了中國人和英國人還有很多個(gè)國家的人,如果這些類都要增加相同的功能,這個(gè)工作量就比較大了,那要如何解決這個(gè)問題呢?這個(gè)時(shí)候,我們就可以引入一個(gè)新類
Human
,然后,將每個(gè)類相同的部分寫在這個(gè)類里面,其他類,諸如
Englisnman
和
Chinese
就可以從
Human
類中繼承而來,那這個(gè)時(shí)候,增添的操作,就只需要在?
Human
類中增加就好了,不需要改動(dòng)
Chinese
和
Englishman
,工作量就小了很多。我們來看?
Human
類的代碼實(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
類之后,我們就可以來實(shí)現(xiàn)我們所說的?
Englishman
和
Chinese
類了,代碼如下所示:
#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
可以看到?
Englishman
和
Chinese
都是繼承自
Human
類,這個(gè)時(shí)候,就不需要再自己實(shí)現(xiàn)
setName
和
getName
了。我們繼續(xù)來完善我們的代碼,先從主函數(shù)說起,主函數(shù)代碼如下所示:
void?test_eating(Human?*h)
{
????h->eating();
}
int?main(int?argc,?char?**argv)
{
????Englishman?e;
????Chinese?c;
????Human?*?h[2]?=?{