欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++多態(tài)的實現(xiàn)及原理詳細解析

 更新時間:2013年09月30日 08:59:43   作者:  
C++的多態(tài)性用一句話概括就是:在基類的函數(shù)前加上virtual關(guān)鍵字,在派生類中重寫該函數(shù),運行時將會根據(jù)對象的實際類型來調(diào)用相應(yīng)的函數(shù)。如果對象類型是派生類,就調(diào)用派生類的函數(shù);如果對象類型是基類,就調(diào)用基類的函數(shù)

1. 用virtual關(guān)鍵字申明的函數(shù)叫做虛函數(shù),虛函數(shù)肯定是類的成員函數(shù)。
2. 存在虛函數(shù)的類都有一個一維的虛函數(shù)表叫做虛表。類的對象有一個指向虛表開始的虛指針。虛表是和類對應(yīng)的,虛表指針是和對象對應(yīng)的。
3. 多態(tài)性是一個接口多種實現(xiàn),是面向?qū)ο蟮暮诵?。分為類的多態(tài)性和函數(shù)的多態(tài)性。
4. 多態(tài)用虛函數(shù)來實現(xiàn),結(jié)合動態(tài)綁定。
5. 純虛函數(shù)是虛函數(shù)再加上= 0。
6. 抽象類是指包括至少一個純虛函數(shù)的類。

純虛函數(shù):virtual void breathe()=0;即抽象類!必須在子類實現(xiàn)這個函數(shù)!即先有名稱,沒內(nèi)容,在派生類實現(xiàn)內(nèi)容!

我們先看一個例子:

復(fù)制代碼 代碼如下:

#include <iostream.h>
class animal
{
public:
       void sleep()
       {
              cout<<"animal sleep"<<endl;
       }
       void breathe()
       {
              cout<<"animal breathe"<<endl;
       }
};
class fish:public animal
{
public:
       void breathe()
       {
              cout<<"fish bubble"<<endl;
       }
};
void main()
{
       fish fh;
       animal *pAn=&fh; // 隱式類型轉(zhuǎn)換
       pAn->breathe();
}

注意,在例1-1的程序中沒有定義虛函數(shù)??紤]一下例1-1的程序執(zhí)行的結(jié)果是什么?
答案是輸出:animal breathe

我們在main()函數(shù)中首先定義了一個fish類的對象fh,接著定義了一個指向animal類的指針變量pAn,將fh的地址賦給了指針變量pAn,然后利用該變量調(diào)用pAn->breathe()。許多學(xué)員往往將這種情況和C++的多態(tài)性搞混淆,認為fh實際上是fish類的對象,應(yīng)該是調(diào)用fish類的breathe(),輸出“fish bubble”,然后結(jié)果卻不是這樣。下面我們從兩個方面來講述原因。

1、 編譯的角度
C++編譯器在編譯的時候,要確定每個對象調(diào)用的函數(shù)(要求此函數(shù)是非虛函數(shù))的地址,這稱為早期綁定(early binding),當我們將fish類的對象fh的地址賦給pAn時,C++編譯器進行了類型轉(zhuǎn)換,此時C++編譯器認為變量pAn保存的就是animal對象的地址。當在main()函數(shù)中執(zhí)行pAn->breathe()時,調(diào)用的當然就是animal對象的breathe函數(shù)。

2、 內(nèi)存模型的角度
我們給出了fish對象內(nèi)存模型,如下圖所示:

我們構(gòu)造fish類的對象時,首先要調(diào)用animal類的構(gòu)造函數(shù)去構(gòu)造animal類的對象,然后才調(diào)用fish類的構(gòu)造函數(shù)完成自身部分的構(gòu)造,從而拼接出一個完整的fish對象。當我們將fish類的對象轉(zhuǎn)換為animal類型時,該對象就被認為是原對象整個內(nèi)存模型的上半部分,也就是圖1-1中的“animal的對象所占內(nèi)存”。那么當我們利用類型轉(zhuǎn)換后的對象指針去調(diào)用它的方法時,當然也就是調(diào)用它所在的內(nèi)存中的方法。因此,輸出animal breathe,也就順理成章了。

正如很多學(xué)員所想,在例1-1的程序中,我們知道pAn實際指向的是fish類的對象,我們希望輸出的結(jié)果是魚的呼吸方法,即調(diào)用fish類的breathe方法。這個時候,就該輪到虛函數(shù)登場了。

前面輸出的結(jié)果是因為編譯器在編譯的時候,就已經(jīng)確定了對象調(diào)用的函數(shù)的地址,要解決這個問題就要使用遲綁定(late binding)技術(shù)。當編譯器使用遲綁定時,就會在運行時再去確定對象的類型以及正確的調(diào)用函數(shù)。而要讓編譯器采用遲綁定,就要在基類中聲明函數(shù)時使用virtual關(guān)鍵字(注意,這是必須的,很多學(xué)員就是因為沒有使用虛函數(shù)而寫出很多錯誤的例子),這樣的函數(shù)我們稱為虛函數(shù)。一旦某個函數(shù)在基類中聲明為virtual,那么在所有的派生類中該函數(shù)都是virtual,而不需要再顯式地聲明為virtual。
下面修改例1-1的代碼,將animal類中的breathe()函數(shù)聲明為virtual,如下:

復(fù)制代碼 代碼如下:

#include <iostream.h>
class animal
{
public:
 void sleep()
 {
  cout<<"animal sleep"<<endl;
 }
 virtual void breathe()
 {
  cout<<"animal breathe"<<endl;
 }
};

class fish:public animal
{
public:
 void breathe()
 {
  cout<<"fish bubble"<<endl;
 }
};
void main()
{
 fish fh;
 animal *pAn=&fh; // 隱式類型轉(zhuǎn)換
 pAn->breathe();
}


大家可以再次運行這個程序,你會發(fā)現(xiàn)結(jié)果是“fish bubble”,也就是根據(jù)對象的類型調(diào)用了正確的函數(shù)。
那么當我們將breathe()聲明為virtual時,在背后發(fā)生了什么呢?

編譯器在編譯的時候,發(fā)現(xiàn)animal類中有虛函數(shù),此時編譯器會為每個包含虛函數(shù)的類創(chuàng)建一個虛表(即vtable),該表是一個一維數(shù)組,在這個數(shù)組中存放每個虛函數(shù)的地址。對于例1-2的程序,animal和fish類都包含了一個虛函數(shù)breathe(),因此編譯器會為這兩個類都建立一個虛表,(即使子類里面沒有virtual函數(shù),但是其父類里面有,所以子類中也有了)如下圖所示:




 

那么如何定位虛表呢?編譯器另外還為每個類的對象提供了一個虛表指針(即vptr),這個指針指向了對象所屬類的虛表。在程序運行時,根據(jù)對象的類型去初始化vptr,從而讓vptr正確的指向所屬類的虛表,從而在調(diào)用虛函數(shù)時,就能夠找到正確的函數(shù)。對于例1-2的程序,由于pAn實際指向的對象類型是fish,因此vptr指向的fish類的vtable,當調(diào)用pAn->breathe()時,根據(jù)虛表中的函數(shù)地址找到的就是fish類的breathe()函數(shù)。

正是由于每個對象調(diào)用的虛函數(shù)都是通過虛表指針來索引的,也就決定了虛表指針的正確初始化是非常重要的。換句話說,在虛表指針沒有正確初始化之前,我們不能夠去調(diào)用虛函數(shù)。那么虛表指針在什么時候,或者說在什么地方初始化呢?

答案是在構(gòu)造函數(shù)中進行虛表的創(chuàng)建和虛表指針的初始化。還記得構(gòu)造函數(shù)的調(diào)用順序嗎,在構(gòu)造子類對象時,要先調(diào)用父類的構(gòu)造函數(shù),此時編譯器只“看到了”父類,并不知道后面是否后還有繼承者,它初始化父類對象的虛表指針,該虛表指針指向父類的虛表。當執(zhí)行子類的構(gòu)造函數(shù)時,子類對象的虛表指針被初始化,指向自身的虛表。對于例2-2的程序來說,當fish類的fh對象構(gòu)造完畢后,其內(nèi)部的虛表指針也就被初始化為指向fish類的虛表。在類型轉(zhuǎn)換后,調(diào)用pAn->breathe(),由于pAn實際指向的是fish類的對象,該對象內(nèi)部的虛表指針指向的是fish類的虛表,因此最終調(diào)用的是fish類的breathe()函數(shù)。

要注意:對于虛函數(shù)調(diào)用來說,每一個對象內(nèi)部都有一個虛表指針,該虛表指針被初始化為本類的虛表。所以在程序中,不管你的對象類型如何轉(zhuǎn)換,但該對象內(nèi)部的虛表指針是固定的,所以呢,才能實現(xiàn)動態(tài)的對象函數(shù)調(diào)用,這就是C++多態(tài)性實現(xiàn)的原理。

總結(jié)(基類有虛函數(shù)):
1. 每一個類都有虛表。

2. 虛表可以繼承,如果子類沒有重寫虛函數(shù),那么子類虛表中仍然會有該函數(shù)的地址,只不過這個地址指向的是基類的虛函數(shù)實現(xiàn)。如果基類有3個虛函數(shù),那么基類的虛表中就有三項(虛函數(shù)地址),派生類也會有虛表,至少有三項,如果重寫了相應(yīng)的虛函數(shù),那么虛表中的地址就會改變,指向自身的虛函數(shù)實現(xiàn)。如果派生類有自己的虛函數(shù),那么虛表中就會添加該項。

3. 派生類的虛表中虛函數(shù)地址的排列順序和基類的虛表中虛函數(shù)地址排列順序相同。

這就是C++中的多態(tài)性。當C++編譯器在編譯的時候,發(fā)現(xiàn)animal類的breathe()函數(shù)是虛函數(shù),這個時候C++就會采用遲綁定(late binding)技術(shù)。也就是編譯時并不確定具體調(diào)用的函數(shù),而是在運行時,依據(jù)對象的類型(在程序中,我們傳遞的fish類對象的地址)來確認調(diào)用的是哪一個函數(shù),這種能力就叫做C++的多態(tài)性。我們沒有在breathe()函數(shù)前加virtual關(guān)鍵字時,C++編譯器在編譯時就確定了哪個函數(shù)被調(diào)用,這叫做早期綁定(early binding)。

C++的多態(tài)性是通過遲綁定技術(shù)來實現(xiàn)的。

C++的多態(tài)性用一句話概括就是:在基類的函數(shù)前加上virtual關(guān)鍵字,在派生類中重寫該函數(shù),運行時將會根據(jù)對象的實際類型來調(diào)用相應(yīng)的函數(shù)。如果對象類型是派生類,就調(diào)用派生類的函數(shù);如果對象類型是基類,就調(diào)用基類的函數(shù)。

虛函數(shù)是在基類中定義的,目的是不確定它的派生類的具體行為。例:
定義一個基類:class Animal//動物。它的函數(shù)為breathe()//呼吸。
再定義一個類class Fish//魚 。它的函數(shù)也為breathe()
再定義一個類class Sheep //羊。它的函數(shù)也為breathe()

為了簡化代碼,將Fish,Sheep定義成基類Animal的派生類。
然而Fish與Sheep的breathe不一樣,一個是在水中通過水來呼吸,一個是直接呼吸空氣。所以基類不能確定該如何定義breathe,所以在基類中只定義了一個virtual breathe,它是一個空的虛函數(shù)。具本的函數(shù)在子類中分別定義。程序一般運行時,找到類,如果它有基類,再找它的基類,最后運行的是基類中的函數(shù),這時,它在基類中找到的是virtual標識的函數(shù),它就會再回到子類中找同名函數(shù)。派生類也叫子類。基類也叫父類。這就是虛函數(shù)的產(chǎn)生,和類的多態(tài)性(breathe)的體現(xiàn)。

這里的多態(tài)性是指類的多態(tài)性。
函數(shù)的多態(tài)性是指一個函數(shù)被定義成多個不同參數(shù)的函數(shù),它們一般被存在頭文件中,當你調(diào)用這個函數(shù),針對不同的參數(shù),就會調(diào)用不同的同名函數(shù)。例:Rect()//矩形。它的參數(shù)可以是兩個坐標點(point,point)也可能是四個坐標(x1,y1,x2,y2)這叫函數(shù)的多態(tài)性與函數(shù)的重載。

類的多態(tài)性,是指用虛函數(shù)和延遲綁定來實現(xiàn)的。函數(shù)的多態(tài)性是函數(shù)的重載。

一般情況下(沒有涉及virtual函數(shù)),當我們用一個指針/引用調(diào)用一個函數(shù)的時候,被調(diào)用的函數(shù)是取決于這個指針/引用的類型。即如果這個指針/引用是基類對象的指針/引用就調(diào)用基類的方法;如果指針/引用是派生類對象的指針/引用就調(diào)用派生類的方法,當然如果派生類中沒有此方法,就會向上到基類里面去尋找相應(yīng)的方法。這些調(diào)用在編譯階段就確定了。

當設(shè)計到多態(tài)性的時候,采用了虛函數(shù)和動態(tài)綁定,此時的調(diào)用就不會在編譯時候確定而是在運行時確定。不在單獨考慮指針/引用的類型而是看指針/引用的對象的類型來判斷函數(shù)的調(diào)用,根據(jù)對象中虛指針指向的虛表中的函數(shù)的地址來確定調(diào)用哪個函數(shù)。

相關(guān)文章

  • C語言實現(xiàn)猜數(shù)字小項目

    C語言實現(xiàn)猜數(shù)字小項目

    這篇文章主要為大家詳細介紹了C語實現(xiàn)猜數(shù)字小項目,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • C語言函數(shù)調(diào)用約定和返回值詳情

    C語言函數(shù)調(diào)用約定和返回值詳情

    這篇文章主要介紹了C語言函數(shù)調(diào)用約定和返回值詳情,函數(shù)調(diào)用約定不同,會影響函數(shù)生成的符號名,函數(shù)入?yún)㈨樞?,形參?nèi)存的清理者,更多相關(guān)需要的小伙伴可以參考下文詳情介紹
    2022-07-07
  • 詳解C++ 拷貝構(gòu)造函數(shù)和賦值運算符

    詳解C++ 拷貝構(gòu)造函數(shù)和賦值運算符

    本文主要介紹了拷貝構(gòu)造函數(shù)和賦值運算符的區(qū)別,以及在什么時候調(diào)用拷貝構(gòu)造函數(shù)、什么情況下調(diào)用賦值運算符。最后,簡單的分析了下深拷貝和淺拷貝的問題。有需要的朋友可以看下
    2016-12-12
  • C++二叉搜索樹BSTree使用詳解

    C++二叉搜索樹BSTree使用詳解

    二叉搜索樹(Binary Search Tree)又稱二叉排序樹,也稱作二叉查找樹它或者是一棵空樹,或者是具有以下性質(zhì)的二叉樹,若它的左子樹不為空,則左子樹上所有節(jié)點的值都小于根節(jié)點的值,若它的右子樹不為空,則右子樹上所有節(jié)點的值都大于根節(jié)點的值
    2023-03-03
  • C語言中bool變量的深入理解

    C語言中bool變量的深入理解

    C語言中沒有BOOL類型變量,它是C++獨有的,由于使用BOOL類型可以使代碼更具有可讀性,下面這篇文章主要給大家介紹了關(guān)于C語言中bool變量的相關(guān)資料,需要的朋友可以參考下
    2021-08-08
  • c++實現(xiàn)簡單的線程池

    c++實現(xiàn)簡單的線程池

    本文介紹的線程池采用C++語言,在windows平臺下實現(xiàn)。本著技術(shù)分享的精神寫作本文同時公布源代碼。歡迎大家指出該線程池存在的問題并對當前性能進行討論。
    2015-03-03
  • C++字符數(shù)組的輸入輸出和字符串結(jié)束標志使用講解

    C++字符數(shù)組的輸入輸出和字符串結(jié)束標志使用講解

    這篇文章主要介紹了C++字符數(shù)組的輸入輸出和符串結(jié)束標志使用講解,是C++入門學(xué)習中的基礎(chǔ)知識,需要的朋友可以參考下
    2015-09-09
  • c語言中字符串與字符串數(shù)組詳解

    c語言中字符串與字符串數(shù)組詳解

    在C語言當中,字符串數(shù)組可以使用char a[] [10]; 或者char *a[]; 表示,下面這篇文章主要給大家介紹了關(guān)于c語言中字符串與字符串數(shù)組的相關(guān)資料,需要的朋友可以參考下
    2021-11-11
  • C++的new和delete詳解

    C++的new和delete詳解

    這篇文章主要介紹的是C++的new和delete,可以這么理解 new相當于是malloc 、delete相當于是free,下面我們就來詳情介紹,,需要的朋友可以參一考
    2021-09-09
  • c++創(chuàng)建二維動態(tài)數(shù)組與內(nèi)存釋放問題

    c++創(chuàng)建二維動態(tài)數(shù)組與內(nèi)存釋放問題

    這篇文章主要介紹了c++創(chuàng)建二維動態(tài)數(shù)組與內(nèi)存釋放問題,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-06-06

最新評論