C++ 派生類函數重載與虛函數繼承詳解


類的關系圖:

image-20220302182444654

一、作用域與名字查找

1.作用域的嵌套

派生類的作用域嵌套在基類之內

Bulk_quote bulk;
cout<< bulk.isbn();

名字isbn解析過程:

  • 因為我們是通過Bulk_quote的對象調用isbn的,所以首先在Bulk_quote中查找,這一步沒有找到名字isbn。
  • 因為 Bulk_quote是Disc_quote 的派生類,所以接下來在Disc_quote中查找,仍然找不到。
  • 因為Disc_quote是Quote的派生類,所以接着查找Quote;此時找到了名字isbn,所以我們使用的isbn最終被解析為Quote中的isbn。

2.在編譯時進行名字查找

成員名字的查找類型由靜態類型決定

//給Disc_quote添加一個成員,返回折扣政策
class Disc_quote : public Quote {
public : 
    std::pair<int ,double) discount_policy() const
    	{return {quantity,discount};}
};

我們只能通過Disc_quote及其派生類對象來使用discount_policy。

Bulk_quote bulk;
Bulk_qoute *bulkP = &bulk;   //靜態類型與動態類型一致
Quote *itemP = &bulk;        //靜態類型為Quote,動態類型不一定
bulkP->discount_policy();    //正確:bulkP的類型是Bulk_quote*
itemP->discount_policy();    //錯誤:itemP的類型是Quote*

盡管在bulk中確實含有一個名為discount_policy的成員,但是該成員對於itemP卻是不可見的。

itemP的類型是Quote的指針,意味着對discount_policy的搜索將從Quote開始

顯然Quote不包含名為discount_policy的成員,所以我們無法通過Quote的對象、引用或指針調用discount_policy。

3.名字沖突與繼承

派生類可以重用基類中的名字,由於派生類的作用域嵌套在基類中,所以會隱藏基類的同名變量

派生類成員隱藏同名的基類成員

struct Base{
    Base():mem(0){}
protected:
    int mem;
};
struct Derived : Base{//struct默認public繼承
    Derived(int i) : mem(i){};
    int get_mem() {return mem;}
protected:
    int mem;
};

get_mem返回的是在Derived中的mem

Derived d(42);
cout<<d.get_mem()<<endl;  //打印42

4.通過作用域運算符來使用隱藏的成員

struct Derived : public Base{
    int get_base_mem() {return Base::mem;}
    //...
};

d.get_base_mem();   //輸出0

二、同名函數隱藏與虛函數覆蓋

1.幾種必須區分的情況

派生類函數形式 與基類同名函數的關系 形參列表 綁定方式
非虛函數 隱藏基類同名函數 可相同可不同 靜態綁定
虛函數 覆蓋基類虛函數 必須相同 動態綁定

使用基類的引用或指針調用虛函數時,會發生動態綁定

  1. 當派生類有基類的同名虛函數且該函數不是虛函數時,無論兩個同名函數的參數是否相同。
    • 由於派生類的作用域嵌套在基類內部,所以都會隱藏同名的基類函數
    • 由於不是虛函數,所以即使兩函數參數相同,也不會發生動態綁定
//情況1舉例
class A{
public :
    //基類的print不是虛函數
    void print() const 
        {cout<<"class A"<<endl;};
};

class B : public A{
public:
    //B隱藏了A的同名函數
    void print() const 
        {cout<<"class B"<<endl;}
};

void Test_Print(const A &a){//傳入基類的指針
    //由於基類的print不是虛函數,所以不會動態綁定
    //.print()的結果取決於a的靜態類型
    //所以無論傳入的是A還是B,都打印class A
    a.print(); 
}

int main(){
    A a;
    B b;
    Test_Print(a);  //打印class A
    Test_Print(b);  //打印class A;因為傳入參數的靜態類型是A
    return 0;
}
  1. 當派生類有基類的同名函數且該函數是虛函數時

    • 參數列表相同,實現覆蓋基類虛函數,可以發生動態綁定

      //情況2:參數列表相同時,虛函數被覆蓋
      class A{
      public :
          //基類的print是虛函數
          void print() const 
              {cout<<"class A"<<endl;};
      };
      
      /*
      class B和Test_Print都不變
      */
      
      int main(){
          A a;
          B b;
          Test_Print(a);  //打印class A
          Test_Print(b);  //打印class B;因為發生了動態綁定
          return 0;
      }
      
    • 參數列表不相同,相當於派生類定義了一個新函數隱藏了基類虛函數,基類的虛函數沒有被覆蓋

      class A{
      public :
          //基類的print是虛函數
          void print() const 
              {cout<<"class A"<<endl;};
      };
      
      class B : public A{
      public:
          //B的print(int i)與基類虛函數同名
          //但參數列表不同,定義了一個新函數
          //基類的虛函數print()沒有被覆蓋
          void print(int i) const 
              {cout<<"print(int i)"<<endl;}
      };
      
      void Test_Print(const A &a){//傳入基類的指針
          a.print(); 
      }
      
      int main(){
          A a;
          B b;
          //打印class A
          Test_Print(a);  
          //打印class A;
          //因為派生類沒有重載虛函數,繼續調用基類虛函數
          Test_Print(b);
          //打印print(int i)
          //調用派生類新增的函數print(int i)
          b.print(42); 
          return 0;
      }
      

2.一個更復雜的例子

例子出自《C++ Primer》P550

//類的定義

class Base{
public : 
    virtual int fcn();
};

class D1 : public Base{
public:
    //隱藏基類的fcn,這個fcn不是虛函數
    //D1繼承了Base::fcn()虛函數的定義
    int fcn(int);		//形參列表與Base中的fcn不一致
    virtual void f2();	//定義一個新的虛函數,它在Base中不存在
};

class D2 : public D1{
public :
    int fcn(int);	//是一個非虛函數,隱藏了D1::fcn(int)
    int fcn();		//覆蓋了虛函數Base::fcn()
    void f2();		//覆蓋了虛函數f2
};
//調用虛函數的例子
//fcn是Base中的虛函數
//D1直接繼承Base的虛函數fcn
//D2重載了Base的fcn
Base bobj;
D1 d1obj;
D2 d2obj;
Base *bp1 = &bobj, *bp2 = &d1jobj, *bp3 = &d2obj;
bp1->fcn();		//虛調用:運行時執行Base::fcn
bp2->fcn();		//虛調用:運行時執行Base::fcn
bp3->fcn();		//虛調用:運行時執行D2::fcn

//f2是D1中的虛函數
//Base中沒有定義f2
//D2重載了D1的虛函數f2
D1 *d1p = &d1obj;
D2 *d2p = &d2obj;
bp2->f2();		//錯誤:Base對象中沒有名為f2的成員
d1p->f2();		//虛調用:執行D1::f2()
d2p->f2();		//虛調用:執行D2::f2()
//調用非虛函數的例子
//fcn(int)是D1中的 非虛函數
//Base中沒有定義fcn(int)
//D2中的fcn(int)隱藏了D1中的fcn(int)
Base *p1 = &d2obj; //d2obj是D2類型的對象
D1 	 *p2 = &d2obj;
D2 	 *p3 = &d2obj;
p1->fcn(42);	//錯誤:Base中沒有接受int的fcn
p2->fcn(42);	//靜態綁定:調用D1::fcn(int)
p3->fcn(42);	//靜態綁定:調用D2::fcn(int)


免責聲明!

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



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