類的關係圖:
一、作用域與名字查詢
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舉例
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;
}
-
當派生類有基類的同名函式且該函式是虛擬函式時
-
引數列表相同,實現覆蓋基類虛擬函式,可以發生動態繫結
//情況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)