從一個面試題來談C++的多態性


 C++編程語言是一款應用廣泛,支持多種程序設計的計算機編程語言。它的繼承、重載、多態等特性為其自身鍍上了一層層神秘的色彩,這也是為什么C++精彩的原因,如今,眾多語言模仿C++的特性,更說明了這樣的性質的獨特之處,我們今天就會為大家詳細介紹其中C++多態性的一些基本知識,以方便大家在學習過程中對此能夠有一個充分的掌握。

下面先上一個復試題目:

#include<iostream>
using namespace std;

class A
{
public:
    void foo()
    {
        printf("1\n");
    }
    virtual void fun()
    {
        printf("2\n");
    }
};
class B : public A
{
public:
    void foo()
    {
        printf("3\n");
    }
    void fun()
    {
        printf("4\n");
    }
};
int main(void)
{
    A a;
    B b;
    A *p = &a;
    p->foo();
    p->fun();
    p = &b;
    p->foo();
    p->fun();
    return 0;
}

要求是寫出該函數的輸出結果。

   當然,對於大牛來說,這中小菜一碟的事情肯定會成為笑柄,但從這個很小的例子,可以看出多態的很重要的性質。沒有疑問,第一個p->foo()和p->fuu()都很好理解,本身是基類指針,指向的又是基類對象,調用的都是基類本身的函數,因此輸出結果就是1、2。第二個輸出結果就是1、4。p->foo()和p->fuu()則是基類指針指向子類對象,正式體現多態的用法,p->foo()由於指針是個基類指針,指向是一個固定偏移量的函數,因此此時指向的就只能是基類的foo()函數的代碼了,因此輸出的結果還是1。而p->fun()指針是基類指針,指向的fun是一個虛函數,由於每個虛函數都有一個虛函數列表,此時p調用fun()並不是直接調用函數,而是通過虛函數列表找到相應的函數的地址,因此根據指向的對象不同,函數地址也將不同,這里將找到對應的子類的fun()函數的地址,因此輸出的結果也會是子類的結果4。

   我們來總結一下:這個題目中可以看出多態的哪些特性呢?

      多態性可以簡單地概括為“一個接口,多種方法”,程序在運行時才決定調用的函數,它是面向對象編程領域的核心概念。多態(polymorphisn),字面意思多種形狀。
  C++多態性是通過虛函數來實現的,虛函數允許子類重新定義成員函數,而子類重新定義父類的做法稱為覆蓋(override),或者稱為重寫。(這里我覺得要補充,重寫的話可以有兩種,直接重寫成員函數和重寫虛函數,只有重寫了虛函數的才能算作是體現了C++多態性)而重載則是允許有多個同名的函數,而這些函數的參數列表不同,允許參數個數不同,參數類型不同,或者兩者都不同。編譯器會根據這些函數的不同列表,將同名的函數的名稱做修飾,從而生成一些不同名稱的預處理函數,來實現同名函數調用時的重載問題。但這並沒有體現多態性。
  多態與非多態的實質區別就是函數地址是早綁定還是晚綁定。如果函數的調用,在編譯器編譯期間就可以確定函數的調用地址,並生產代碼,是靜態的,就是說地址是早綁定的。而如果函數調用的地址不能在編譯器期間確定,需要在運行時才確定,這就屬於晚綁定。
  那么多態的作用是什么呢,封裝可以使得代碼模塊化,繼承可以擴展已存在的代碼,他們的目的都是為了代碼重用。而多態的目的則是為了接口重用。也就是說,不論傳遞過來的究竟是那個類的對象,函數都能夠通過同一個接口調用到適應各自對象的實現方法。

  最常見的用法就是聲明基類的指針,利用該指針指向任意一個子類對象,調用相應的虛函數,可以根據指向的子類的不同而實現不同的方法。如果沒有使用虛函數的話,即沒有利用C++多態性,則利用基類指針調用相應的函數的時候,將總被限制在基類函數本身,而無法調用到子類中被重寫過的函數。因為沒有多態性,函數調用的地址將是一定的,而固定的地址將始終調用到同一個函數,這就無法實現一個接口,多種方法的目的了。

      那么,如果我們把上面main函數中return語句前面添加上這么幾句:B *ptr = (B *)&a;  ptr->foo();  ptr->fun();這兩次用的結果是怎么樣的呢?

      這是一個用子類的指針去指向一個強制轉換為子類地址的基類對象。結果,這兩句調用的輸出結果是3,2。
  並不是很理解這種用法,從原理上來解釋,由於B是子類指針,雖然被賦予了基類對象地址,但是ptr->foo()在調用的時候,由於地址偏移量固定,偏移量是子類對象的偏移量,於是即使在指向了一個基類對象的情況下,還是調用到了子類的函數,雖然可能從始到終都沒有子類對象的實例化出現。而ptr->fun()的調用,可能還是因為C++多態性的原因,由於指向的是一個基類對象,通過虛函數列表的引用,找到了基類中fun()函數的地址,因此調用了基類的函數。由此可見多態性的強大,可以適應各種變化,不論指針是基類的還是子類的,都能找到正確的實現方法。

下面我們再看另一個例子,來尋找多態的隱藏規則:

#include<iostream>  
using namespace std;  
  
class Base  
{  
public:  
    virtual void f(float x)  
    {  
        cout<<"Base::f(float)"<< x <<endl;  
    }  
    void g(float x)  
    {  
        cout<<"Base::g(float)"<< x <<endl;  
    }  
    void h(float x)  
    {  
        cout<<"Base::h(float)"<< x <<endl;  
    }  
};  
class Derived : public Base  
{  
public:  
    virtual void f(float x)  
    {  
        cout<<"Derived::f(float)"<< x <<endl;   //多態、覆蓋  
    }  
    void g(int x)  
    {  
        cout<<"Derived::g(int)"<< x <<endl;     //隱藏  
    }  
    void h(float x)  
    {  
        cout<<"Derived::h(float)"<< x <<endl;   //隱藏  
    }  
};  
int main(void)  
{  
    Derived d;  
    Base *pb = &d;  
    Derived *pd = &d;  
    // Good : behavior depends solely on type of the object  
    pb->f(3.14f);   // Derived::f(float) 3.14  
    pd->f(3.14f);   // Derived::f(float) 3.14  
  
    // Bad : behavior depends on type of the pointer  
    pb->g(3.14f);   // Base::g(float)  3.14  
    pd->g(3.14f);   // Derived::g(int) 3   
  
    // Bad : behavior depends on type of the pointer  
    pb->h(3.14f);   // Base::h(float) 3.14  
    pd->h(3.14f);   // Derived::h(float) 3.14  
    return 0;  
} 

   從這個例子,我們可以知道

      1、有virtual才可能發生多態現象

      2、不發生多態(無virtual)調用就按原類型調用

所以,多態的隱藏規則非常令人迷惑
本來僅僅區別重載與覆蓋並不算困難,但是C++的隱藏規則使問題復雜性陡然增加。
這里“隱藏”是指派生類的函數屏蔽了與其同名的基類函數,規則如下:
(1)如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual
關鍵字,基類的函數將被隱藏(注意別與重載混淆)。
(2)如果派生類的函數與基類的函數同名,並且參數也相同,但是基類函數沒有virtual
關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)。
上面的程序中:
(1)函數Derived::f(float)覆蓋了Base::f(float)。
(2)函數Derived::g(int)隱藏了Base::g(float),而不是重載。
(3)函數Derived::h(float)隱藏了Base::h(float),而不是覆

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM