目錄
類的關系圖:
一、作用域與名字查找
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)