C++編程中的虛函數(shù)

我們知道姻蚓,在同一類中是不能定義兩個(gè)名字相同竿屹、參數(shù)個(gè)數(shù)和類型都相同的函數(shù)的茁帽,否則就是“重復(fù)定義”悦屏。但是在類的繼承層次結(jié)構(gòu)中防症,在不同的層次中可以出現(xiàn)名字相同评甜、參數(shù)個(gè)數(shù)和類型都相同而功能不同的函數(shù)肘交。

人們提出這樣的設(shè)想氯窍,能否用同一個(gè)調(diào)用形式缔杉,既能調(diào)用派生類又能調(diào)用基類的同名函數(shù)锤躁。在程序中不是通過不同的對(duì)象名去調(diào)用不同派生層次中的同名函

數(shù),而是通過指針調(diào)用它們或详。例如系羞,用同一個(gè)語句“pt->display(

);”可以調(diào)用不同派生層次中的display函數(shù),只需在調(diào)用前給指針變量 pt 賦以不同的值(使之指向不同的類對(duì)象)即可霸琴。

打個(gè)比方椒振,你要去某一地方辦事,如果乘坐公交車梧乘,必須事先確定目的地澎迎,然后乘坐能夠到達(dá)目的地的公交車線路。如果改為乘出租車选调,就簡(jiǎn)單多了夹供,不必查

行車路線,因?yàn)槌鲎廛囀裁吹胤蕉寄苋パ纾灰谏宪嚭笈R時(shí)告訴司機(jī)要到哪里即可罩引。如果想訪問多個(gè)目的地,只要在到達(dá)一個(gè)目的地后再告訴司機(jī)下一個(gè)目的地即

可枝笨,顯然袁铐,“打的”要比乘公交車 方便揭蜒。無論到什么地方去都可以乘同—輛出租車。這就是通過同一種形式能達(dá)到不同目的的例子剔桨。

C++中的虛函數(shù)就是用來解決這個(gè)問題的屉更。虛函數(shù)的作用是允許在派生類中重新定義與基類同名的函數(shù),并且可以通過基類指針或引用來訪問基類和派生類中的同名函數(shù)洒缀。

請(qǐng)分析下面這個(gè)例子瑰谜。這個(gè)例子開始時(shí)沒有使用虛函數(shù),然后再討論使用虛函數(shù)的情況树绩。

[例] 基類與派生類中有同名函數(shù)萨脑。在下面的程序中Student是基類,Graduate是派生類饺饭,它們都有display這個(gè)同名的函數(shù)渤早。


#include

#include

using namespace std;

//聲明基類Student

class Student

{

public:

Student(int, string,float);//聲明構(gòu)造函數(shù)

void display( );//聲明輸出函數(shù)

protected://受保護(hù)成員,派生類可以訪問

int num;

string name;

float score;

};

//Student類成員函數(shù)的實(shí)現(xiàn)

Student::Student(int n, string nam,float s)//定義構(gòu)造函數(shù)

{

num=n;

name=nam;

score=s;

}

void Student::display( )//定義輸出函數(shù)

{

cout<<"num:"<< num << "\n name:" << name << "\n score:" << score << "\n\n";

}

//聲明公用派生類Graduate

class Graduate:publicStudent

{

public:

Graduate(int, string,float,float);//聲明構(gòu)造函數(shù)

void display( );//聲明輸出函數(shù)

private:float pay;

};

// Graduate類成員函數(shù)的實(shí)現(xiàn)

void Graduate::display( )//定義輸出函數(shù)

{

cout<<"num:"<< num << "\n name:" << name << "\n score:" << score << "\n pay=" << pay <<endl;

}

Graduate::Graduate(int n, string nam,float s,float p):Student(n,nam,s),pay(p){}

//主函數(shù)

int main()

{

Student stud1(1001,"Li",87.5);//定義Student類對(duì)象stud1

Graduate grad1(2001,"Wang",98.5,563.5);//定義Graduate類對(duì)象grad1

Student *pt=&stud1;//定義指向基類對(duì)象的指針變量pt

pt->display( );

pt=&grad1;

pt->display( );

return0;

}

運(yùn)行結(jié)果如下:

num:1001(stud1的數(shù)據(jù))

name:Li

score:87.5

num:2001 (grad1中基類部分的數(shù)據(jù))

name:wang

score:98.5

假如想輸出grad1的全部數(shù)據(jù)成員瘫俊,當(dāng)然也可以采用這樣的方法:通過對(duì)象名調(diào)用display函數(shù)鹊杖,如grad1.display(),或者定義

一個(gè)指向Graduate類對(duì)象的指針變量ptr扛芽,然后使ptr指向gradl骂蓖,再用ptr->display()調(diào)用。這當(dāng)然是可以的川尖,但是如果

該基類有多個(gè)派生類登下,每個(gè)派生類又產(chǎn)生新的派生類,形成了同一基類的類族空厌。每個(gè)派生類都有同名函數(shù)display庐船,在程序中要調(diào)用同一類族中不同類的同名

函數(shù),就要定義多個(gè)指向各派生類的指針變量嘲更。這兩種辦法都不方便筐钟,它要求在調(diào)用不同派生類的同名函數(shù)時(shí)采用不同的調(diào)用方式,正如同前面所說的那樣赋朦,到不同

的目的地要乘坐指定的不同的公交車篓冲,一一 對(duì)應(yīng),不能搞錯(cuò)宠哄。如果能夠用同一種方式去調(diào)用同一類族中不同類的所有的同名函數(shù)壹将,那就好了。

用虛函數(shù)就能順利地解決這個(gè)問題毛嫉。下面對(duì)程序作一點(diǎn)修改诽俯,在Student類中聲明display函數(shù)時(shí),在最左面加一個(gè)關(guān)鍵字virtual承粤,即

virtual void display( );

這樣就把Student類的display函數(shù)聲明為虛函數(shù)暴区。程序其他部分都不改動(dòng)闯团。再編譯和運(yùn)行程序,請(qǐng)注意分析運(yùn)行結(jié)果:

num:1001(stud1的數(shù)據(jù))

name:Li

score:87.5

num:2001 (grad1中基類部分的數(shù)據(jù))

name:wang

score:98.5

pay=563.5 (這一項(xiàng)以前是沒有的)

看仙粱!這就是虛函數(shù)的奇妙作用》拷唬現(xiàn)在用同一個(gè)指針變量(指向基類對(duì)象的指針變量),不但輸出了學(xué)生stud1的全部數(shù)據(jù)伐割,而且還輸出了研究生

grad1的全部數(shù)據(jù)候味,說明已調(diào)用了grad1的display函數(shù)。用同一種調(diào)用形式“pt->display()”隔心,而且pt是同一個(gè)基類指

針白群,可以調(diào)用同一類族中不同類的虛函數(shù)。這就是多態(tài)性济炎,對(duì)同一消息川抡,不同對(duì)象有 不同的響應(yīng)方式辐真。

說明:本來基類指針是用來指向基類對(duì)象的须尚,如果用它指向派生類對(duì)象,則進(jìn)行指針類型轉(zhuǎn)換侍咱,將派生類對(duì)象的指針先轉(zhuǎn)換為基類的指針耐床,所以基類指針指向

的是派生類對(duì)象中的基類部分。在程序修改前楔脯,是無法通過基類指針去調(diào)用派生類對(duì)象中的成員函數(shù)的撩轰。虛函數(shù)突破了這一限制,在派生類的基類部分中昧廷,派生類的

虛函數(shù)取代了基類原來的虛函數(shù)堪嫂,因此在使基類指針指向派生類對(duì)象后,調(diào)用虛函數(shù)時(shí)就調(diào)用了派生類的虛函數(shù)皆串。

要注意的是,只有用virtual聲明了虛函數(shù)后才具有以上作用眉枕。如果不聲明為虛函數(shù)恶复,企圖通過基類指針調(diào)用派生類的非虛函數(shù)是不行的。

虛函數(shù)的以上功能是很有實(shí)用意義的速挑。在面向?qū)ο蟮某绦蛟O(shè)計(jì)中谤牡,經(jīng)常會(huì)用到類的繼承,目的是保留基類的特性姥宝,以減少新類開發(fā)的時(shí)間翅萤。但是,從基類繼承

來的某些成員函數(shù)不完全適應(yīng)派生類的需要腊满,例如在例中套么,基類的display函數(shù)只輸出基類的數(shù)據(jù)流纹,而派生類的display函數(shù)需要輸出派生類的數(shù)據(jù)。

過去我們?cè)?jīng)使派生類的輸出函數(shù)與基類的輸出函數(shù)不同名(如display和display1)违诗,但如果派生的層次多漱凝,就要起許多不同的函數(shù)名,很不方

便诸迟。如果采用同名函數(shù)茸炒,又會(huì)發(fā)生同名覆蓋。

利用虛函數(shù)就很好地解決了這個(gè)問題阵苇”诠可以看到:當(dāng)把基類的某個(gè)成員函數(shù)聲明為虛函數(shù)后,允許在其派生類中對(duì)該函數(shù)重新定義绅项,賦予它新的功能紊册,并且可

以通過指向基類的指針指向同一類族中不同類的對(duì)象,從而調(diào)用其中的同名函數(shù)快耿。由虛函數(shù)實(shí)現(xiàn)的動(dòng)態(tài)多態(tài)性就是:同一類族中不同類的對(duì)象囊陡,對(duì)同一函數(shù)調(diào)用作出

不同的響應(yīng)。

虛函數(shù)的使用方法是:

在基類用virtual聲明成員函數(shù)為虛函數(shù)掀亥。

這樣就可以在派生類中重新定義此函數(shù)撞反,為它賦予新的功能,并能方便地被調(diào)用搪花。在類外定義虛函數(shù)時(shí)遏片,不必再加virtual。

在派生類中重新定義此函數(shù)撮竿,要求函數(shù)名吮便、函數(shù)類型、函數(shù)參數(shù)個(gè)數(shù)和類型全部與基類的虛函數(shù)相同幢踏,并根據(jù)派生類的需要重新定義函數(shù)體髓需。

C++規(guī)定,當(dāng)一個(gè)成員函數(shù)被聲明為虛函數(shù)后惑折,其派生類中的同名函數(shù)都自動(dòng)成為虛函數(shù)授账。因此在派生類重新聲明該虛函數(shù)時(shí),可以加virtual惨驶,也可以不加白热,但習(xí)慣上一般在每一層聲明該函數(shù)時(shí)都加virtual,使程序更加清晰粗卜。如果在派生類中沒有對(duì)基類的虛函數(shù)重新定義屋确,則派生類簡(jiǎn)單地繼承其直接基類的虛函數(shù)。

定義一個(gè)指向基類對(duì)象的指針變量,并使它指向同一類族中需要調(diào)用該函數(shù)的對(duì)象攻臀。

通過該指針變量調(diào)用此虛函數(shù)焕数,此時(shí)調(diào)用的就是指針變量指向的對(duì)象的同名函數(shù)。

通過虛函數(shù)與指向基類對(duì)象的指針變量的配合使用刨啸,就能方便地調(diào)用同一類族中不同類的同名函數(shù)堡赔,只要先用基類指針指向即可。如果指針不斷地指向同一類族中不同類的對(duì)象设联,就能不斷地調(diào)用這些對(duì)象中的同名函數(shù)善已。這就如同前面說的,不斷地告訴出租車司機(jī)要去的目的地离例,然后司機(jī)把你送到你要去的地方换团。

需要說明;有時(shí)在基類中定義的非虛函數(shù)會(huì)在派生類中被重新定義(如例中的area函數(shù))宫蛆,如果用基類指針調(diào)用該成員函數(shù)艘包,則系統(tǒng)會(huì)調(diào)用對(duì)象中基類部

分的成員函數(shù);如果用派生類指針調(diào)用該成員函數(shù)耀盗,則系統(tǒng)會(huì)調(diào)用派生類對(duì)象中的成員函數(shù)想虎,這并不是多態(tài)性行為(使用的是不同類型的指針),沒有用到虛函數(shù)的

功能袍冷。

以前介紹的函數(shù)重載處理的是同一層次上的同名函數(shù)問題磷醋,而虛函數(shù)處理的是不同派生層次上的同名函數(shù)問題,前者是橫向重載胡诗,后者可以理解為縱向重載。但與重載不同的是:同一類族的虛函數(shù)的首部是相同的淌友,而函數(shù)重載時(shí)函數(shù)的首部是不同的(參數(shù)個(gè)數(shù)或類型不同)煌恢。

在什么情況下應(yīng)當(dāng)聲明虛函數(shù)

使用虛函數(shù)時(shí),有兩點(diǎn)要注意:

只能用virtual聲明類的成員函數(shù)震庭,使它成為虛函數(shù)瑰抵,而不能將類外的普通函數(shù)聲明為虛函數(shù)。因?yàn)樘摵瘮?shù)的作用是允許在派生類中對(duì)基類的虛函數(shù)重新定義器联。顯然二汛,它只能用于類的繼承層次結(jié)構(gòu)中。

一個(gè)成員函數(shù)被聲明為虛函數(shù)后拨拓,在同一類族中的類就不能再定義一個(gè)非virtual的但與該虛函數(shù)具有相同的參數(shù)(包括個(gè)數(shù)和類型)和函數(shù)返回值類型的同名函數(shù)肴颊。

根據(jù)什么考慮是否把一個(gè)成員函數(shù)聲明為虛函數(shù)呢?主要考慮以下幾點(diǎn):

首先看成員函數(shù)所在的類是否會(huì)作為基類渣磷。然后看成員函數(shù)在類的繼承后有無可能被更改功能婿着,如果希望更改其功能的,一般應(yīng)該將它聲明為虛函數(shù)。

如果成員函數(shù)在類被繼承后功能不需修改竟宋,或派生類用不到該函數(shù)提完,則不要把它聲明為虛函數(shù)。不要僅僅考慮到要作為基類而把類中的所有成員函數(shù)都聲明為虛函數(shù)丘侠。

應(yīng)考慮對(duì)成員函數(shù)的調(diào)用是通過對(duì)象名還是通過基類指針或引用去訪問徒欣,如果是通過基類指針或引用去訪問的,則應(yīng)當(dāng)聲明為虛函數(shù)蜗字。

有時(shí)帚称,在定義虛函數(shù)時(shí),并不定義其函數(shù)體秽澳,即函數(shù)體是空的闯睹。它的作用只是定義了一個(gè)虛函數(shù)名,具體功能留給派生類去添加担神。

需要說明的是:使用虛函數(shù)楼吃,系統(tǒng)要有一定的空間開銷。當(dāng)一個(gè)類帶有虛函數(shù)時(shí)妄讯,編譯系統(tǒng)會(huì)為

該類構(gòu)造一個(gè)虛函數(shù)表(virtual function table孩锡,簡(jiǎn)稱vtable),它是一個(gè)指針數(shù)組亥贸,存放每個(gè)虛函數(shù)的入口地址躬窜。系統(tǒng)在進(jìn)行動(dòng)態(tài)關(guān)聯(lián)時(shí)的時(shí)間開銷是很少的,因此炕置,多態(tài)性是高效的荣挨。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市朴摊,隨后出現(xiàn)的幾起案子默垄,更是在濱河造成了極大的恐慌,老刑警劉巖甚纲,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件口锭,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡介杆,警方通過查閱死者的電腦和手機(jī)鹃操,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來春哨,“玉大人荆隘,你說我怎么就攤上這事”ィ” “怎么了臭胜?”我有些...
    開封第一講書人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵莫其,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我耸三,道長(zhǎng)乱陡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任仪壮,我火速辦了婚禮憨颠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘积锅。我一直安慰自己爽彤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開白布缚陷。 她就那樣靜靜地躺著适篙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪箫爷。 梳的紋絲不亂的頭發(fā)上嚷节,一...
    開封第一講書人閱讀 52,158評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音虎锚,去河邊找鬼硫痰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛窜护,可吹牛的內(nèi)容都是我干的效斑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼柱徙,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼缓屠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起坐搔,我...
    開封第一講書人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤藏研,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后概行,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡弧岳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年凳忙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片禽炬。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涧卵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出腹尖,到底是詐尸還是另有隱情柳恐,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站乐设,受9級(jí)特大地震影響讼庇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜近尚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一蠕啄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧戈锻,春花似錦歼跟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拒迅,卻和暖如春骚秦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坪它。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工骤竹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人往毡。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓蒙揣,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親开瞭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子懒震,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容