4.5 C++重載、覆蓋和遮蔽

來源:互聯網
上載者:User

標籤:執行   拷貝構造   str   語句   code   space   重載   style   rtu   

參考:http://www.weixueyuan.net/view/6375.html

總結:

  函數簽名包括函數名和函數參數的個數、順序以及參數資料類型。

  需要注意的是函數簽名並不包含函數傳回值部分,如果兩個函數僅僅只有函數傳回值不同,那麼系統是無法區分這兩個函數的,此時編譯器會提示法錯誤。

  函數重載是指兩個函數具有相同的函數名,但是函數參數個數或參數類型不同。函數重載多發生在頂層函數之間或者同一個類中,函數重載不需要構成繼承關係。

  函數重載是編譯期綁定,它並不是多態。

  覆蓋構成條件和多態構成條件是相同的,覆蓋是一種函數間的表現關係,而多態描述的是函數的一種性質,二者所描述的其實是同一種文法現象。
  覆蓋首先要求有繼承關係,其次是要求構成繼承關係的兩個類中必須具有相同函數簽名的成員函數,並且這兩個成員函數必須是虛成員函數,具備這兩個條件後,衍生類別中的虛成員函數則會覆蓋基類中的同名的虛成員函數。如果我們通過基類指標或引用來調用虛成員函數,則會形成多態。

  函數覆蓋屬於運行期綁定,但是要注意如果函數不是虛函數,則無論採用什麼方法調用函數均為編譯期綁定。

  函數遮蔽同樣要求構成繼承關係,構成繼承關係的兩個類中具有相同函數名的函數,如果這兩個函數不夠成覆蓋關係,則就構成了遮蔽關係。(注意不是相同函數簽名,只需要相同函數名就可以了)

  覆蓋要求的是函數簽名相同,而遮蔽只需要函數名相同。

 

  一般來講,函數名相同通常會用在以下幾種情況中:

  • 頂層函數的函數重載。對於程式設計人員而言,實現功能相同但所處理資料類型不同的函數時,採用函數重載的方式將會帶來極大的方便。例如設計一個求絕對值函數,針對整型和double類型各設及一個abs函數,調用時而無需關注參數類型,這樣的設計是很方便的。
  • 類中的成員函數的重載,這種函數重載和頂層函數重載同樣能給我們的程式帶來方便。
  • 類中的建構函式重載,設計多個建構函式,用於不同的初始化對象方式。
  • 在繼承層次中為了使用多態特性而採用相同函數簽名。


除此之外函數名相同還會導致繼承層次中的函數遮蔽,而函數遮蔽這一特性通常會使得程式難以理解,因此建議謹慎使用函數遮蔽機制。

 

------------------------------------

多態函數是指在運行期才將函數入口地址與函數名綁定的函數,僅有虛函數才是多態。但是除了虛函數以外,重載和遮蔽同樣具有函數名相同的特徵,在此做一下區分。為了說明方便,我們引入函數簽名這一概念。函數簽名包括函數名和函數參數的個數、順序以及參數資料類型。

例1:

void f( )void g( )void f(int)

例2:

void f( int)void f(double)

例3:

void f(double, int)void f(int, double)

為了理解函數簽名的含義,我們先來看一下上面的三個例子。例1中函數f()和函數g()函數名不同,因此這兩個函數的函數簽名不同,f()函數和f(int)函數一個有參數,一個沒有參數,函數簽名同樣不同,g()函數和f(int)函數函數名不同並且函數參數個數也不同,因此這兩個函數的函數簽名也是不相同的。例2中兩個函數函數名相同,函數參數個數相同,但是函數參數的類型不同,因此這兩個函數的函數簽名也不是相同的。例3中的兩個函數,函數名相同,函數參數個數相同,函數參數類型也是相同的,都是一個double類型和一個int類型的,只不過函數參數的順序是不相同,如此一來這兩個函數的函數簽名同樣是不相同的。


需要注意的是函數簽名並不包含函數傳回值部分,如果兩個函數僅僅只有函數傳回值不同,那麼系統是無法區分這兩個函數的,此時編譯器會提示法錯誤。

例4:

int f(int, double)void f(int, double)

 

在本例中,兩個函數的函數名相同,函數參數個數相同,函數參數類型相同,函數參數順序相同,如此一來兩個函數的函數簽名是相同的。但是這兩個函數的傳回值不同,僅憑函數傳回值,編譯器無法區分這兩個函數,編譯器提示法錯誤。

瞭解了函數簽名的含義之後我們再來看一下重載、覆蓋和遮蔽。

1) 重載

函數重載是指兩個函數具有相同的函數名,但是函數參數個數或參數類型不同。函數重載多發生在頂層函數之間或者同一個類中,函數重載不需要構成繼承關係。

例5:

class base{public :    base();    base(int a);    base(int a, int b);    base( base &);    int fun(int a);    int fun(double a);    int fun(int a, int b);private:    int x;    int y;};int g(int a);int g(double a);int g(int a, int b);

 

在本例中,我們列出了幾種函數重載的情形。首先是函數的建構函式重載,我們在類中聲明了四個建構函式,這四個函數構成重載的關係,前面三個函數之間只是函數參數數目不同,第四個建構函式為拷貝建構函式,該函數與前面的預設建構函式和兩個帶參建構函式參數類型不同。類中的成員函數同樣可以進行重載,如本例中base類的三個fun函數。這兩種情況是類內部的函數重載,在類外部頂層函數也同樣能夠成函數重載關係,如本例中的g函數,這三個函數都是頂層函數,由於函數名相同,但是函數參數不同,構成函數重載關係。

函數重載是編譯期綁定,它並不是多態。

2) 覆蓋

覆蓋構成條件和多態構成條件是相同的,覆蓋是一種函數間的表現關係,而多態描述的是函數的一種性質,二者所描述的其實是同一種文法現象。

覆蓋首先要求有繼承關係,其次是要求構成繼承關係的兩個類中必須具有相同函數簽名的成員函數,並且這兩個成員函數必須是虛成員函數,具備這兩個條件後,衍生類別中的虛成員函數則會覆蓋基類中的同名的虛成員函數。如果我們通過基類指標或引用來調用虛成員函數,則會形成多態。

例6:

#include<iostream>using namespace std;class base{public :    virtual void vir1(){}    virtual void vir2(){}};class derived : public base{public:    void vir1(){}    void vir2(){}};int main(){    base * p;    p = new derived;    p->vir1();    p->vir2();    delete p;    return 0;}

 

本例是一個非常簡單的多態的樣本程式,base類和derived類構成繼承關係,在這兩個類中成員函數vir1和vir2同名,並且這兩個同名函數都被聲明為了虛函數。如此一來則構成了函數覆蓋,衍生類別中的vir1函數覆蓋了基類中的vir1函數,衍生類別中的vir2函數覆蓋了基類中的vir2函數。在主函數中通過基類指標調用vir1和vir2虛函數,構成多態,這兩個函數的運行為運行期綁定。

函數覆蓋屬於運行期綁定,但是要注意如果函數不是虛函數,則無論採用什麼方法調用函數均為編譯期綁定。如果我們將例6中的基類中的兩個virtual關鍵字去掉,則主函數中調用vir1和vir2函數屬於編譯期綁定,無論p指向的是衍生類別對象或者是基類對象,執行的都將會是基類的vir1和vir2函數。

3) 遮蔽

函數遮蔽同樣要求構成繼承關係,構成繼承關係的兩個類中具有相同函數名的函數,如果這兩個函數不夠成覆蓋關係,則就構成了遮蔽關係。遮蔽理解起來很簡單,只要衍生類別與基類中具有相同函數名(注意不是相同函數簽名,只需要相同函數名就可以了)並且不構成覆蓋關係即為遮蔽。

遮蔽可以分為兩種情況,一種是非虛函數之間,另一種則是虛函數之間。我們通過程式樣本來分別介紹這兩種遮蔽情況。

例7:

#include<iostream>using namespace std;class base{public :    void vir1(){cout<<"base vir1"<<endl;}    void vir2(){cout<<"base vir2"<<endl;}};class derived : public base{public:    void vir1(){cout<<"derived vir1"<<endl;}    void vir2(int){cout<<"derived vir2"<<endl;}};int main(){    base * p;    p = new derived;    p->vir1();    p->vir2();    delete p;    derived d;    d.vir1();    d.vir2(5);        d.base::vir1();        d.base::vir2();    return 0;}

 

在本例中沒有虛函數,base類和derived類構成繼承關係,因為構成繼承關係的兩個類中有同名函數,因此構成了函數遮蔽。衍生類別中的vir1函數遮蔽了基類中的vir1函數,衍生類別中的vir2函數遮蔽了基類中的vir1函數。需要注意的是雖然衍生類別中的vir2函數和基類中的vir2函數的函數簽名不同,但是只需要函數名相同就構成函數遮蔽。我們接著來分析一下主函數,主函數中我們先是定義了基類類型的指標,指標指向的是基類對象,然後通過指標調用函數vir1和vir2,這個時候因為並不構成多態,因此調用的還是基類的vir1和vir2函數。之後定義了一個衍生類別對象d,通過該對象調用vir1和vir2函數,因為衍生類別中的vir1和vir2遮蔽了基類中的vir1和vir2函數,因此直接調用的將會是衍生類別中的vir1和vir2函數。如果需要通過衍生類別對象調用被遮蔽的基類中的函數,則需要通過域解析操作符來處理,在本例的最後d.base::vir1();和d.base::vir2()就是這麼做的。這個程式的最終運行結果如下:
    base vir1
    base vir2
    derived vir1
    derived vir2
    base vir1
    base vir2

如果構成繼承關係的兩個類中包含同名的虛函數,則情況非常複雜,當然要判斷還是非常簡單,還是那個原則:如果沒有構成覆蓋則為遮蔽。覆蓋要求的是函數簽名相同,而遮蔽只需要函數名相同。

例8:

#include<iostream>using namespace std;class base{public :    virtual void vir1(){cout<<"base vir1"<<endl;}    virtual void vir2(){cout<<"base vir2"<<endl;}};class derived : public base{public:    virtual void vir1(){cout<<"derived vir1"<<endl;}    virtual void vir2(int){cout<<"derived vir2"<<endl;}};int main(){    base * p;    p = new derived;    p->vir1();    p->vir2();    delete p;    derived d;    d.vir1();    d.vir2(5);    d.base::vir1();    d.base::vir2();    return 0;}

 

在這個程式中,定義了兩個類,base類和derived類,這兩個類構成繼承關係,衍生類別和基類中包含同名的函數,並且同名的函數均為虛函數。針對這兩個同名函數,我們一個一個來分析一下,首先來看一下vir1,基類和衍生類別中的vir1函數的函數簽名是相同的,而且又是虛函數,構成了函數覆蓋關係。再來看一下vir2函數,基類中的vir2函數和衍生類別中的vir2函數函數名相同,但函數參數不同,則它們的函數簽名不同,因此衍生類別中的vir2函數和基類中的vir1函數不構成函數覆蓋,既然函數名相同,那麼可以構成函數遮蔽。

接著我們同樣來看一下主函數,在主函數中,我們定義了一個基類類型的指標,指標指向衍生類別對象,之後通過該指標分別調用vir1和vir2函數。由於vir1是構成函數覆蓋,因此通過基類指標調用vir1構成多態,由於p指標指向的是衍生類別對象,故調用的vir1函數是衍生類別中的vir1函數。衍生類別中的vir2函數和基類中的vir2函數只構成函數遮蔽,因此通過基類類型指標調用vir2函數並不會形成多態,最終調用的是基類中的vir2函數。之後定義了衍生類別對象d,通過衍生類別對象d調用的函數只能是衍生類別中的函數,當然也包括從基類中繼承來的函數。d.vir1()和d.vir2(5)這兩個函數調用語句調用的都是衍生類別中新增的成員函數,衍生類別中的vir1函數雖然和基類中的vir1函數構成覆蓋關係,但是由於沒有通過基類指標或引用來調用,因此也沒有構成多態,如此一來,如果需要通過對象來調用從基類中繼承過來的vir1函數,同樣是需要域解析操作符。衍生類別中的vir2函數和基類中vir2函數構成遮蔽,因此通過對象和成員選擇符調用的仍是衍生類別中新增的vir2函數,如果想調用基類中的vir2函數,則需要通過域解析操作符。例8程式運行結果如下:
    derived vir1
    base vir2
    derived vir1
    derived vir2
    base vir1
    base vir2
   
以上總結了函數名相同的所有情況,函數名相同利用的好可以為程式設計帶來較大的便利,使用的不好則容易誤導程式設計人員。一般來講,函數名相同通常會用在以下幾種情況中:

  • 頂層函數的函數重載。對於程式設計人員而言,實現功能相同但所處理資料類型不同的函數時,採用函數重載的方式將會帶來極大的方便。例如設計一個求絕對值函數,針對整型和double類型各設及一個abs函數,調用時而無需關注參數類型,這樣的設計是很方便的。
  • 類中的成員函數的重載,這種函數重載和頂層函數重載同樣能給我們的程式帶來方便。
  • 類中的建構函式重載,設計多個建構函式,用於不同的初始化對象方式。
  • 在繼承層次中為了使用多態特性而採用相同函數簽名。


除此之外函數名相同還會導致繼承層次中的函數遮蔽,而函數遮蔽這一特性通常會使得程式難以理解,因此建議謹慎使用函數遮蔽機制。

4.5 C++重載、覆蓋和遮蔽

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.