1. 程式人生 > >C++多型深入理解

C++多型深入理解

1. 用virtual關鍵字申明的函式叫做虛擬函式,虛擬函式肯定是類的成員函式。
2. 存在虛擬函式的類都有一個一維的虛擬函式表叫做虛表。類的物件有一個指向虛表開始的虛指標。虛表是和類對應的,虛表指標是和物件對應的。
3. 多型性是一個介面多種實現,是面向物件的核心。分為類的多型性和函式的多型性。
4. 多型用虛擬函式來實現,結合動態繫結。
5. 純虛擬函式是虛擬函式再加上= 0。
6. 抽象類是指包括至少一個純虛擬函式的類。


純虛擬函式:virtual void breathe()=0;即抽象類!必須在子類實現這個函式!即先有名稱,沒內容,在派生類實現內容!


我們先看一個例子:例1- 1
#include
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;// 隱式型別轉換
       pAn->breathe();
}
注意,在例1-1的程式中沒有定義虛擬函式。考慮一下例1-1的程式執行的結果是什麼?
答案是輸出:animal breathe
我 們在main()函式中首先定義了一個fish類的物件fh,接著定義了一個指向animal類的指標變數pAn,將fh的地址賦給了指標變數pAn,然 後利用該變數呼叫pAn->breathe()。許多學員往往將這種情況和C++的多型性搞混淆,認為fh實際上是fish類的物件,應該是呼叫 fish類的breathe(),輸出“fish bubble”,然後結果卻不是這樣。下面我們從兩個方面來講述原因。
1、 編譯的角度

C++編譯器在編譯的時候,要確定每個物件呼叫的函式(要求此函式是非虛擬函式)的地址,這稱為早期繫結(early binding),當我們將fish類的物件fh的地址賦給pAn時,C++編譯器進行了型別轉換,此時C++編譯器認為變數pAn儲存的就是animal物件的地址。當在main()函式中執行pAn->breathe()時,呼叫的當然就是animal物件的breathe函式。

我 們構造fish類的物件時,首先要呼叫animal類的建構函式去構造animal類的物件,然後才呼叫fish類的建構函式完成自身部分的構造,從而拼 接出一個完整的fish物件。當我們將fish類的物件轉換為animal型別時,該物件就被認為是原物件整個記憶體模型的上半部分,也就是圖1-1中的 “animal的物件所佔記憶體”。那麼當我們利用型別轉換後的物件指標去呼叫它的方法時,當然也就是呼叫它所在的記憶體中的方法。因此,輸出animal breathe,也就順理成章了。
正如很多學員所想,在例1-1的程式中,我們知道pAn實際指向的是fish類的物件,我們希望輸出的結果是魚的呼吸方法,即呼叫fish類的breathe方法。這個時候,就該輪到虛擬函式登場了。
前面輸出的結果是因為編譯器在編譯的時候,就已經確定了物件呼叫的函式的地址,要解決這個問題就要使用遲繫結(late binding)技術。當編譯器使用遲繫結時,就會在執行時再去確定物件的型別以及正確的呼叫函式。而要讓編譯器採用遲繫結,就要在基類中宣告函式時使用virtual關鍵字(注意,這是必須的,很多學員就是因為沒有使用虛擬函式而寫出很多錯誤的例子),這樣的函式我們稱為虛擬函式。一旦某個函式在基類中宣告為virtual,那麼在所有的派生類中該函式都是virtual,而不需要再顯式地宣告為virtual。
下面修改例1-1的程式碼,將animal類中的breathe()函式宣告為virtual,如下:
例1- 2
#include
class animal
{
public:
       void sleep()
       {
              cout<<"animal sleep"<<endl;
       }
       virtualvoid breathe()
       {
              cout<<"animal breathe"<<endl;
       }
};


class fish:public animal
{
public:
       void breathe()
       {
              cout<<"fish bubble"<<endl;
       }
};
void main()
{
       fish fh;
       animal *pAn=&fh; // 隱式型別轉換
       pAn->breathe();
}
大家可以再次執行這個程式,你會發現結果是“fish bubble”,也就是根據物件的型別呼叫了正確的函式。
那麼當我們將breathe()宣告為virtual時,在背後發生了什麼呢?
編譯器在編譯的時候,發現animal類中有虛擬函式,此時編譯器會為每個包含虛擬函式的類建立一個虛表(即vtable),該表是一個一維陣列,在這個陣列中存放每個虛擬函式的地址。對於例1-2的程式,animal和fish類都包含了一個虛擬函式breathe(),因此編譯器會為這兩個類都建立一個虛表,(即使子類裡面沒有virtual函式,但是其父類裡面有,所以子類中也有了)

那麼如何定位虛表呢?編譯器另外還為每個類的物件提供了一個虛表指標(即vptr),這個指標指向了物件所屬類的虛表。在 程式執行時,根據物件的型別去初始化vptr,從而讓vptr正確的指向所屬類的虛表,從而在呼叫虛擬函式時,就能夠找到正確的函式。對於例1-2的程式, 由於pAn實際指向的物件型別是fish,因此vptr指向的fish類的vtable,當呼叫pAn->breathe()時,根據虛表中的函式 地址找到的就是fish類的breathe()函式。
正是由於每個物件呼叫的虛擬函式都是通過虛表指標來索引的,也就決定了虛表指標的正確初始化是非常重要的。換句話說,在虛表指標沒有正確初始化之前,我們不能夠去呼叫虛擬函式。那麼虛表指標在什麼時候,或者說在什麼地方初始化呢?
答案是在建構函式中進行虛表的建立和虛表指標的初始化。還 記得建構函式的呼叫順序嗎,在構造子類物件時,要先呼叫父類的建構函式,此時編譯器只“看到了”父類,並不知道後面是否後還有繼承者,它初始化父類物件的 虛表指標,該虛表指標指向父類的虛表。當執行子類的建構函式時,子類物件的虛表指標被初始化,指向自身的虛表。對於例2-2的程式來說,當fish類的 fh物件構造完畢後,其內部的虛表指標也就被初始化為指向fish類的虛表。在型別轉換後,呼叫pAn->breathe(),由於pAn實際指向 的是fish類的物件,該物件內部的虛表指標指向的是fish類的虛表,因此最終呼叫的是fish類的breathe()函式。
要注意:對於虛擬函式呼叫來說,每一個物件內部都有一個虛表指標,該虛表指標被初始化為本類的虛表。所以在程式中,不管你的物件型別如何轉換,但該物件內部的虛表指標是固定的,所以呢,才能實現動態的物件函式呼叫,這就是C++多型性實現的原理。
總結(基類有虛擬函式):
1. 每一個類都有虛表。
2. 虛表可以繼承,如果子類沒有重寫虛擬函式,那麼子類虛表中仍然會有該函式的地址,只不過這個地址指向的是基類的虛擬函式實現。如果基類有3個虛擬函式,那麼基類 的虛表中就有三項(虛擬函式地址),派生類也會有虛表,至少有三項,如果重寫了相應的虛擬函式,那麼虛表中的地址就會改變,指向自身的虛擬函式實現。如果派生類 有自己的虛擬函式,那麼虛表中就會新增該項。
3. 派生類的虛表中虛擬函式地址的排列順序和基類的虛表中虛擬函式地址排列順序相同。
這 就是C++中的多型性。當C++編譯器在編譯的時候,發現animal類的breathe()函式是虛擬函式,這個時候C++就會採用遲繫結(late binding)技術。也就是編譯時並不確定具體呼叫的函式,而是在執行時,依據物件的型別(在程式中,我們傳遞的fish類物件的地址)來確認呼叫的是 哪一個函式,這種能力就叫做C++的多型性。我們沒有在breathe()函式前加virtual關鍵字時,C++編譯器在編譯時就確定了哪個函式被調 用,這叫做早期繫結(early binding)。


C++的多型性是通過遲繫結技術來實現的。


C++的多型性用一句話概括就是:在基類的函式前加上virtual關鍵字,在派生類中重寫該函式,執行時將會根據物件的實際型別來呼叫相應的函式。如果物件型別是派生類,就呼叫派生類的函式;如果物件型別是基類,就呼叫基類的函式。


虛擬函式是在基類中定義的,目的是不確定它的派生類的具體行為。例:
定義一個基類:class Animal//動物。它的函式為breathe()//呼吸。
再定義一個類class Fish//魚 。它的函式也為breathe()
再定義一個類class Sheep //羊。它的函式也為breathe()
為了簡化程式碼,將Fish,Sheep定義成基類Animal的派生類。
然 而Fish與Sheep的breathe不一樣,一個是在水中通過水來呼吸,一個是直接呼吸空氣。所以基類不能確定該如何定義breathe,所以在基類 中只定義了一個virtual breathe,它是一個空的虛擬函式。具本的函式在子類中分別定義。程式一般執行時,找到類,如果它有基類,再找它的基類,最後執行的是基類中的函式,這 時,它在基類中找到的是virtual標識的函式,它就會再回到子類中找同名函式。派生類也叫子類。基類也叫父類。這就是虛擬函式的產生,和類的多型性 (breathe)的體現。


這裡的多型性是指類的多型性。
函式的多型性是指一個函式被定義成多個不同引數的函式,它們一般被存在 標頭檔案中,當你呼叫這個函式,針對不同的引數,就會呼叫不同的同名函式。例:Rect()//矩形。它的引數可以是兩個座標點(point,point) 也可能是四個座標(x1,y1,x2,y2)這叫函式的多型性與函式的過載。


類的多型性,是指用虛擬函式和延遲繫結來實現的。函式的多型性是函式的過載。


一般情況下(沒有涉及virtual函式),當我們用一個指標/引用呼叫一個函式的時候,被呼叫的函式是取決於這個指標/引用的型別。即如果這個指標/引用是基類物件的指標/引用就呼叫基類的方法;如果指標/引用是派生類物件的指標/引用就呼叫派生類的方法,當然如果派生類中沒有此方法,就會向上到基類裡面去尋找相應的方法。這些呼叫在編譯階段就確定了。


當設計到多型性的時候,採用了虛擬函式和動態繫結,此時的呼叫就不會在編譯時候確定而是在執行時確定。不在單獨考慮指標/引用的型別而是看指標/引用的物件的型別來判斷函式的呼叫,根據物件中虛指標指向的虛表中的函式的地址來確定呼叫哪個函式。
 
虛擬函式聯絡到多型,多型聯絡到繼承。所以本文中都是在繼承層次上做文章。沒了繼承,什麼都沒得談。 下面是對C++的虛擬函式這玩意兒的理解。


什麼是虛擬函式(如果不知道虛擬函式為何物,但有急切的想知道,那你就應該從這裡開始)


簡單地說,那些被virtual關鍵字修飾的成員函式,就是虛擬函式。虛擬函式的作用,用專業術語來解釋就是實現多型性(Polymorphism),多型性是將介面與實現進行分離;用形象的語言來解釋就是實現以共同的方法,但因個體差異而採用不同的策略。下面來看一段簡單的程式碼


class A{
public:
void print(){ cout<<”This is A”<<endl;}
};


class B:public A{
public:
void print(){ cout<<”This is B”<<endl;}
};


int main(){ //為了在以後便於區分,我這段main()程式碼叫做main1
A a;
B b;
a.print();
b.print();
}


通 過class A和class B的print()這個介面,可以看出這兩個class因個體的差異而採用了不同的策略,輸出的結果也是我們預料中的,分別是This is A和This is B。但這是否真正做到了多型性呢?No,多型還有個關鍵之處就是一切用指向基類的指標或引用來操作物件。那現在就把main()處的程式碼改一改。


int main(){ //main2
A a;
B b;
A* p1=&a;
A* p2=&b;
p1->print();
p2->print();
}


執行一下看看結果,喲呵,驀然回首,結果卻是兩個This is A。問題來了,p2明明指向的是class B的物件但卻是呼叫的class A的print()函式,這不是我們所期望的結果,那麼解決這個問題就需要用到虛擬函式


class A{
public:
virtual void print(){ cout<<”This is A”<<endl;} //現在成了虛函數了
};


class B:public A{
public:
void print(){ cout<<”This is B”<<endl;} //這裡需要在前面加上關鍵字virtual嗎?
};


毫無疑問,class A的成員函式print()已經成了虛擬函式,那麼class B的print()成了虛函數了嗎?回答是Yes,我們只需在把基類的成員函式設為virtual,其派生類的相應的函式也會自動變為虛擬函式。所以,class B的print()也成了虛擬函式。那麼對於在派生類的相應函式前是否需要用virtual關鍵字修飾,那就是你自己的問題了。


現在重新執行main2的程式碼,這樣輸出的結果就是This is A和This is B了。


現在來消化一下,我作個簡單的總結,指向基類的指標在操作它的多型類物件時,會根據不同的類物件,呼叫其相應的函式,這個函式就是虛擬函式 。


純虛擬函式
將breathe()函式申明為純虛擬函式,結果如例2-18所示。
例2-18
class animal
{
public:
    void eat()
    {
        cout<<"animal eat"<<endl;
    }
    void sleep()
    {
        cout<<"animal sleep"<<endl;
    }
    virtual void breathe() = 0;
};


純虛擬函式是指被標明為不具體實現的虛成員函式(注意:純虛擬函式也可以有函式體,但這種提供函式體的用法很少見)。純虛擬函式可以讓類先具有一個操作名稱,而沒有操作內容,讓派生類在繼承時再去具體地給出定義。凡是含有純虛擬函式的類叫做抽象類。這種類不能宣告物件,只是作為基類為派生類服務。在派生類中必須完全實現基類的純虛擬函式,否則,派生類也變成了抽象類,不能例項化物件。


純虛擬函式多用在一些方法行為的設計上。在設計基類時,不太好確定或將來的行為多種多樣,而此行為又是必需的,我們就可以在基類的設計中,以純虛擬函式來宣告此種行為,而不具體實現它。


注意:C++的多型性是由虛擬函式來實現的,而不是純虛擬函式。在子類中如果有對基類虛擬函式的覆蓋定義,無論該覆蓋定義是否有virtual關鍵字,都是虛擬函式。


無虛擬函式的類,在編譯的時候採用的是靜態繫結,即繫結類函式的實現和呼叫,這種繫結是基於指向物件的指標(或是引用)的型別。動態繫結對成員函式的選擇不是基於指標或者引用,而是基於物件型別,不同的物件型別將做出不同的編譯結果。


什麼是虛擬函式和多型
虛擬函式是在類中被宣告為virtual的成員函式,當編譯器看到通過指標或引用呼叫此類函式時,對其執行晚繫結,即通過指標(或引用)指向的物件的型別資訊來決定該函式是哪個類的。通常此類指標或引用都宣告為基類的,它可以指向基類或派生類的物件。


多型指同一個方法根據其所屬的不同物件可以有不同的行為(根據自己理解,不知這麼說是否嚴謹)。


在進入主題之前先介紹一下聯編的概念。聯編就是將模組或者函式合併在一起生成可 執行程式碼的處理過程,同時對每個模組或者函式呼叫分配記憶體地址,並且對外部訪問也分配正確的記憶體地址。按照聯編所進行的階段不同,可分為兩種不同的聯編方法:靜態聯編和動態聯編。在編譯階段就將函式實現和函式呼叫關聯起來稱之為靜態聯編,靜態聯編在編譯階段就必須瞭解所有的函式或模組執行所需要檢測的資訊,它對函式的選擇是基於指向物件的指標(或者引用)的型別。反之在程式執行的時候才進行這種關聯稱之為動態聯編,動態聯編對成員函式的選擇不是基於指標或者引用,而是基於物件型別,不同的物件型別將做出不同的編譯結果。C語言中,所有的聯編都是靜態聯編。C++中一般情況下聯編也是靜態聯編,但是一旦涉及到多型性和虛擬函式就必須使用動態聯編。
多型性是面向物件的核心,它的最主要的思想就是可以採用多種形式的能力,通過一個使用者名稱字或者使用者介面完成不同的實現。通常多型性被簡單的描述為“一個介面,多個實現”。在C++裡面具體的表現為通過基類指標訪問派生類的函式和方法。


揭密動態聯編的祕密
編譯器到底做了什麼實現的虛擬函式的動態編聯呢?我們來探個究竟。


編譯器對每個包含虛擬函式的類建立一個表(稱為V TA B L E)。 在V TA B L E中,編譯器放置特定類的虛擬函式地址。在每個帶有虛擬函式的類中,編譯器祕密地置一指標,稱為v p o i n t e r(縮寫為V P T R),指向這個物件的V TA B L E。通過基類指標做虛擬函式呼叫時(也就是做多型呼叫時),編譯器靜態地插入取得這個V P T R,並在V TA B L E表中查詢函式地址的程式碼,這樣就能呼叫正確的函式使動態聯編髮生。為每個類設定V TA B L E、初始化V P T R、為虛擬函式呼叫插入程式碼,所有這些都是自動發生的,所以我們不必擔心這些。利用虛擬函式,這個物件的合適的函式就能被呼叫,哪怕在編譯器還不知道這個物件 的特定型別的情況下。(《C++程式設計思想》)


在任何類中不存在顯示的型別資訊,可物件中必須存放類資訊,否則型別不可能在執行時建立。那這個類資訊是什麼呢?我們來看下面幾個類:


class no_virtual
{
public:
    void fun1() const{}
    int fun2() const { return a; }
private:
    int a;
}


class one_virtual
{
public:
    virtual void fun1() const{}
    int fun2() const { return a; }
private:
    int a;
}


class two_virtual
{
public:
    virtual void fun1() const{}
    virtual int fun2() const { return a; }
private:
    int a;
}


    以上三個類中:
    no_virtual沒有虛擬函式,sizeof(no_virtual)=4,類no_virtual的長度就是其成員變數整型a的長度;
    one_virtual有一個虛擬函式,sizeof(one_virtual)=8;
    two_virtual 有兩個虛擬函式,sizeof(two_virtual)=8; 有一個虛擬函式和兩個虛擬函式的類的長度沒有區別,其實它們的長度就是no_virtual的長度加一個void指標的長度,它反映出,如果有一個或多個虛函 數,編譯器在這個結構中插入一個指標( V P T R)。在one_virtual 和two_virtual之間沒有區別。這是因為V P T R指向一個存放地址的表,只需要一個指標,因為所有虛擬函式地址都包含在這個表中。


    這個VPTR就可以看作類的型別資訊。


    那我們來看看編譯器是怎麼建立VPTR指向的這個虛擬函式表的。先看下面兩個類:
class base
{
public:
    void bfun(){}
    virtual void vfun1(){}
    virtual int vfun2(){}
private:
    int a;
}


class derived : public base
{
public:
    void dfun(){}
    virtual void vfun1(){}
    virtual int vfun3(){}
private:
    int b;
}


兩個類VPTR指向的虛擬函式表(VTABLE)分別如下:
base類
                      ——————
VPTR——> |&base::vfun1 |
                      ——————
                 |&base::vfun2 |
                  ——————
    
derived類
                      ———————
VPTR——> |&derived::vfun1 |
                      ———————
                  |&base::vfun2    |
                  ———————
                  |&derived::vfun3 |
                   ———————
每當建立一個包含有虛擬函式的類或從包含有虛擬函式的類派生一個類時,編譯器就為這個類建立一個VTABLE,如 上圖所示。在這個表中,編譯器放置了在這個類中或在它的基類中所有已宣告為virtual的函式的地址。如果在這個派生類中沒有對在基類中宣告為 virtual的函式進行重新定義,編譯器就使用基類的這個虛擬函式地址。(在derived的VTABLE中,vfun2的入口就是這種情況。)然後編譯 器在這個類中放置VPTR。當使用簡單繼承時,對於每個物件只有一個VPTR。VPTR必須被初始化為指向相應的VTABLE,這在建構函式中發生。
一旦VPTR被初始化為指向相應的VTABLE,物件就"知道"它自己是什麼型別。但只有當虛擬函式被呼叫時這種自我認知才有用。


    VPTR常常位於物件的開頭,編譯器能很容易地取到VPTR的值, 從而確定VTABLE的位置。VPTR總指向VTABLE的開始地址,所有基類和它的子類的虛擬函式地址(子類自己定義的虛擬函式除外)在VTABLE中儲存 的位置總是相同的,如上面base類和derived類的VTABLE中 vfun1和vfun2的地址總是按相同的順序儲存。編譯器知道vfun1位於VPTR處,vfun2位於VPTR+1處,因此在用基類指標呼叫虛擬函式 時,編譯器首先獲取指標指向物件的型別資訊(VPTR),然後就去呼叫虛擬函式。如一個base類指標pBase指向了一個derived物件,那 pBase->vfun2()被編譯器翻譯為 VPTR+1 的呼叫,因為虛擬函式vfun2的地址在VTABLE中位於索引為1的位置上。同理,pBase->vfun3()被編譯器翻譯為VPTR+2的調 用。這就是所謂的晚繫結。


我們來看一下虛擬函式呼叫的彙編程式碼,以加深理解。
void test(base* pBase)
{
pBase->vfun2();
}


int main(int argc, char* argv[])
{
derived td;
test(&td);
return 0;
}


derived td;編譯生成的彙編程式碼如下:
mov DWORD PTR _td$[esp+24], OFFSET FLAT:

[email protected]@[email protected] ; derived::`vftable'
由編譯器的註釋可知,此時PTR _td$[esp+24]中儲存的就是derived類的VTABLE地址。


test(&td);編譯生成的彙編程式碼如下:
lea eax, DWORD PTR _td$[esp+24]  
mov DWORD PTR __$EHRec$[esp+32], 0
push eax
call [email protected]@[email protected]@@Z   ; test
呼叫test函式時完成了如下工作:取物件td的地址,將其壓棧,然後呼叫test。
pBase->vfun2();編譯生成的彙編程式碼如下:
mov ecx, DWORD PTR _pBase$[esp-4]
mov eax, DWORD PTR [ecx]
jmp DWORD PTR [eax+4]
首 先從棧中取出pBase指標指向的物件地址賦給ecx,然後取物件開頭的指標變數中的地址賦給eax,此時eax的值即為VPTR的值,也就是 VTABLE的地址。最後就是呼叫虛函數了,由於vfun2位於VTABLE的第二個位置,相當於 VPTR+1,每個函式指標是4個位元組長,所以最後的呼叫被編譯器翻譯為 jmp DWORD PTR [eax+4]。如果是呼叫pBase->vfun1(),這句就該被編譯為jmp DWORD PTR [eax]。

相關推薦

C++深入理解

1. 用virtual關鍵字申明的函式叫做虛擬函式,虛擬函式肯定是類的成員函式。 2. 存在虛擬函式的類都有一個一維的虛擬函式表叫做虛表。類的物件有一個指向虛表開始的虛指標。虛表是和類對應的,虛表指標是和物件對應的。 3. 多型性是一個介面多種實現,是面向物件的核心。分為類

一個易錯的面試題來加深對C++理解【轉】

    原題目程式比較長, 我來簡化成核心的考察部分, 如下: #include <iostream> using namespace std; class A { public: virtual void x() { cout <<

C++深入虛擬函式,理解晚繫結

 C++的多型特性是通過晚繫結實現的。晚繫結(late binding),指的是編譯器或直譯器程式在執行前,不知道物件的型別。使用晚繫結,無需檢查物件的型別,只需要檢查物件是否支援特性和方法即可。  在C++中,晚繫結通常發生在使用virtual宣告成員函式時

C++深入CRTP,理解編譯期的

虛擬函式帶來的額外CPU消耗  考慮如下的程式碼: class D { public: int num; D(int i = 0) { num = i; } virtual void print() { cout <<

C++、介面和虛基類的深入理解

表述一:在面嚮物件語言中,介面的多種不同實現方式即為多型。多型是指,用父類的指標指向子類的例項(物件),然後通過父類的指標呼叫實際子類的成員函式。 表述二:基類指標(或引用)的多種狀態,即基類指標所指物件的真實身份為基類則調基類中的函式表現基類的行為,為派生類則調派生類的函式表現為派生類的行為。作用是使基類

繼承、理解 - C#

11月3日 陰天  前兩天看某位大牛寫的程式,對於C#多型有困惑,今天一大早來查閱了不少資料,自認為有了一個基本的認知,記錄下來,一掃今天這陰霾的天氣 ----------------------------------------------------------------- 我

C++學習之對理解

最近學習C++多型及子類記憶體結構,有一些理解與看法,記錄下來 1.多型產生,虛擬函式,虛擬函式指標,虛擬函式表 這一部分不詳細描述,個人參考的書籍是Siddhartha Rao的<21天學通C++>的第11章:多型 瞭解了編譯器利用虛擬函式表與物件的虛擬函式指標來實現多型的

C++性的理解和舉例

多型性是面向物件程式的一個重要特徵,下面通過程式來理解程式的多型性: //多型性 #include<iostream> using namespace std; class Point { public: Point(float x=0,float y=0

C++面向物件理解

多型與遞迴類似,不管是書中還是老師授課,都把其講得神乎其神,讓讀者一頭霧水,莫名其妙。多型實際上非常簡單,學習的難點在於在接觸多型之前,缺乏一個感性的認識。 多型允許將子類的物件當作基類的物件使用,某基型別的引用指向其子型別的物件,呼叫的方法是該子型別的方法。這裡引用和呼叫方法在程式碼編譯前就已經決

C++性的理解

根據清華大學鄭莉老師的書《C++語言程式設計》來總結 1 多型:是指類中具有相似功能的不同函式,使用同一個名稱來實現;是對類的行為再抽象;多型是通過過載函式和虛擬函式來實現的。 2 繼承討論的是類與類的層次關係,多型則是考慮在不同層次的類中,以及在同一個類的

C++中對純虛擬函式和理解

    抽象類是一種特殊的類,它是為了抽象和設計的目的為建立的,它處於繼承層次結構的較上層。       ⑴抽象類的定義:       稱帶有純虛擬函式的類為抽象類。       ⑵抽象類的作用:       抽象類的主要作用是將有關的操作作為結果介面組織在

C++】理解

一.多型的概念 簡單的講就是同一事物在不同條件下所呈現出來的不同形態 舉例:火車站的同一視窗成人售票就是全價票,學生就是半價票。這就是同一事物,但是在不同的條件下可以呈現處不同的形態。有點見人說人話,見鬼說鬼話的意思。 二.多型的實現 #include<io

Java泛深入理解

此外 都沒有 操作 方法調用 length 整形 推薦 如何使用 連接 泛型之前 在面向對象編程語言中,多態算是一種泛化機制。例如,你可以將方法的參數類型設置為基類,那麽該方法就可以接受從這個基類中導出的任何類作為參數,這樣的方法將會更具有通用性。此外,如果將方法參數聲

php理解

php是面向物件的指令碼語言,而我們都知道,面向物件的語言具有三大特性:封裝,繼承,多型。php理應具有這三大特性。   封裝是類的構建過程,php具有;php也具有繼承的特性。唯獨這個多型,php體現的十分模糊。原因是php是弱型別語言。   java的多型體現的十分清晰,大體分兩類:父類引用指向子類物

理解_java學習

1.多型的體現:     父類的引用指向了自己的子類物件;     父類的引用也可以接收自己的子類物件; 2.多型的前提:     必須是類與類之間有關係,要麼繼承、要麼覆蓋;    &

C++ 淺析

本文將圍繞下面四個問題,進行逐一闡述: 1、什麼是多型;2、多型有什麼用;3、多型的原理是什麼;4、如何實現多型。   1、什麼是多型? (1)通過繼承同一個基類,產生了相關的不同的派生類,與基類中同名的成員函式在不同的派生類中會有不同的實現,也就是說:一個介面、多種方法。

C++呼叫實現原理(虛擬函式表詳解)

1.帶有虛擬函式的基類物件模型 我們先看段程式碼: #include<iostream> using namespace std; class B1 { public: void func1() {} int _b; }; class B2 { pub

C++與繼承基本知識詳解

一、類繼承 C++是一種面向物件的語言,最重要的一個目的就是——提供可重用的程式碼,而類繼承就是C++提供來擴充套件和修改類的方法。類繼承就是從已有的類中派生出新的類,派生類繼承了基類的特性,同時可以新增自己的特性。實際上,類與類之間的關係分為三種:代理、組合和繼承。以下是三種關係的圖解

C++,虛擬函式,虛擬函式表,純虛擬函式

1、多型性   指相同物件收到不同訊息或不同物件收到相同訊息時產生不同的實現動作。   C++支援兩種多型性:編譯時多型性,執行時多型性。    a、編譯時多型性:通過過載函式實現 ,模板(2次編譯)   b、執行時多型性:通過

小白學JAVA,與你們感同身受,JAVA---day5:關於理解和分析。魯迅的一句話:總之歲月漫長,然而值得等待。

魯迅的一句話:總之歲月漫長,然而值得等待。 至於是不是他說的,就看大家的了。   /* 多型:事物存在的多種形態。 多型的前提: 1.要有繼承關係。 2.要有方法的重寫。 3.要有父類引用指向子類物件。 向上轉型和向下轉型: 1.父類引用指向子類物件   &nbs