C++ 派生類函式過載與虛擬函式繼承詳解

咪啪魔女發表於2022-03-03

類的關係圖:

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)

相關文章