引言:
在繼承情況下,派生類的作用域巢狀在基類作用域中:如果不能在派生類作用域中確定名字,就在外圍基類作用域中查詢該名字的定義。
正是這種類作用域的層次巢狀使我們能夠直接訪問基類的成員,就好像這些成員是派生類成員一樣:
bulk_item bulk;
cout << bulk.book() << endl;
名字book的使用將這樣確定[先派生->後基類]:
1)bulk是bulk_item類物件,在bulk_item類中查詢,找不到名字book。
2)因為從item_base派生bulk_item,所以接著在item_base類中查詢,找到名字book,則引用成功的確定了。
一、名字查詢在編譯時發生
物件、引用或指標的靜態型別決定了物件能夠完成的行為。甚至當靜態型別和動態型別可能不同的時候,就像使用基類型別的引用或指標時可能會發生的,靜態型別仍然決定著可以使用什麼成員:
class bulk_item : public item_base
};
只能通過disc_item型別或disc_item派生型別的物件、指標或引用訪問discount_policy():
bulk_item bulk;
bulk_item *bulkp = &bulk;
item_base *itemp = &bulk;
bulkp -> discount_policy(); //ok
itemp -> discount_policy(); //error
通過itemp訪問是錯誤的,因為基類型別的指標(引用或物件)只能訪問物件的基類部分,而不能訪問派生類部分,而在基類中又沒有定義discount_policy()成員。
二、名字衝突與繼承
與基類成員同名的派生類成員將遮蔽對基類成員的直接訪問:
class base
protected:
int mem;
};class derived : public base
int get_mem() const
private:
int mem; //將會遮蔽base::mem
};
get_mem中對mem的引用被確定為derive中的名字:
derived d(
43
);
cout << d.get_mem() << endl;
//output 43
可以使用作用域操作符訪問被遮蔽的成員:
class derived : public base
//as before
};//測試
derived d(43);
cout << d.get_mem() << endl; //output 0
作用域操作符指示編譯器在base中查詢mem成員。
【最佳實踐】
設計派生類時,只要可能,最好避免與基類成員的名字衝突!
class base
;class derived : public base
;void derived::foobar()
bool derived::bar(derived *pb)
int main()
/*說明:可能是g++編譯器對型別檢查比較嚴格,這個程式在g++編譯器上死活編譯不過,
*因為在derivd中的string bar處編譯器提示說:與前面的宣告衝突了!
*的確,在derivd中,bar既有資料成員又有成員函式!!!
*/
三、作用域與成員函式
在基類和派生類中使用同一名字的成員函式,其行為與資料成員一樣:在派生類作用域中派生類成員將遮蔽基類成員。即使函式原型不同,基類成員也會被遮蔽:
struct base
;struct derived : base
;int main()
derived中的memfuc宣告隱藏了base中的宣告。在確定下面一條語句時:
d.memfuc();
編譯器查詢名字memfuc,並在derived類中找到。一旦找到了名字,編譯器要就不再繼續查詢了。
【小心地雷】
區域性作用域中宣告的函式不會過載全域性作用域中定義的函式,同樣,派生類中定義的函式也不會過載基類中定義的成員。通過派生類物件呼叫函式時,實參必須與派生類中定義的版本相匹配,只有在派生類中根本沒有定義該函式時,才考慮基類函式。如:
struct base
;struct derived : base
;derived d;
d.memfuc(); //error
如果將derived中的intmemfuc(int)注釋掉,則:
d.memfuc();
//ok
過載函式
像其他任意函式一樣,成員函式(無論虛還是非虛)也可以過載。派生類可以重定義所繼承的0個或多個版本。
[注意] 如果派生類重定義了過載成員,則通過派生型別只能訪問派生類中重定義的那些成員!
struct derived : base
;int main()
如果派生類想通過自身型別使用所有的過載版本,則派生類必須要麼重定義所有過載版本,要麼乙個也不重定義。
有時類需要僅僅重定義乙個過載版本,並且想要繼承其他版本的含義,在這種情況下,派生類不用重定義所繼承的每乙個基類版本,它可以為過載成員提供using宣告。乙個using宣告只能指定乙個名字,不能指定形參表,因此:using宣告會將該函式的所有過載例項加到派生類的作用域。將所有名字加入作用域之後,派生類只需要重定義本型別確實必須定義的那些函式,對其他版本可以使用繼承的定義。
struct base
;struct derived : base
;int main()
四、虛函式與作用域
虛函式:如果基類成員與派生類成員接受的實參不同,就沒有辦法通過基類型別的引用或指標呼叫派生類函式:
class base
;class d1 : public base
;class d2 : public d1
;
通過基類呼叫被遮蔽的虛函式
通過基類型別的引用或指標呼叫函式時,編譯器將在基類中查詢該函式而忽略派生類:
base bobj;
d1 d1obj;
d2 d2obj;
base *bp1 = &bobj,*bp2 = &d1obj,*bp3 = &d2obj;
bp1 -> fcn(); //呼叫base::fcn()
bp2 -> fcn(); //呼叫base::fcn()
bp3 -> fcn(); //呼叫d2::fcn()
【關鍵概念:名字查詢與繼承】
理解 c++中繼承層次的關鍵在於理解如何確定函式呼叫。確定函式呼叫遵循以下四個步驟:
1)首先確定進行函式呼叫的物件、引用或指標的靜態型別
2)在該類中查詢函式,如果找不到,就在直接基類中查詢,如此循著類的繼承鏈往上找,直到找到該函式或者查詢完最後乙個類。如果不能在類或其相關基類中找到該名字,則呼叫是錯誤的。
3)一旦找到了該名字,就進行常規型別檢查,檢視如果給定找到的定義,該函式呼叫是否合法。
4)假定函式呼叫合法,編譯器就生成**。如果函式是虛函式且通過引用或指標呼叫,則編譯器生成**以確定根據物件的動態型別執行哪個函式版本,否則,編譯器生成**直接呼叫函式。
bulk_item bulk;
item_base item(bulk);
item_base *p = &bulk;
/**由於net_price為虛函式
*對虛函式而言,只能通過指標或引用進行動態繫結
*而通過物件呼叫虛函式,所呼叫到的總是該物件所屬型別中定義的函式
*/p -> net_price(10); //呼叫bulk_item版本的net_price
item.net_price(10); //呼叫item_base版本的net_price
多個else if巢狀時的作用域
分別給學生成績排乙個等級,90分以上為a,80分以上為b,70分以上為c,60分以上為d 以下則為不及格。題很簡單,但是中間包含的有隱藏條件,70以上指的是 70,80 其它區域也是乙個道理。題很簡單,主要想說一下作用域 includeint main void else if x 80 else ...
繼承中類的作用域
派生類的作用域巢狀在其基類的作用域之內,如果乙個名字無法在派生類的作用域內無法正確解析,則編譯器將繼續在外層的基類作用域中尋找該名字的定義。派生類中能重定義在其直接基類或間接基類中的名字,此時定義在內層作用域 即派生類 的名字將隱藏定義在外層作用域 即基類 的名字。struct base prote...
繼承中的類作用域 1
每個類定義自己的作用域,當存在繼承關係時,派生類的作用域巢狀在其基類的作用域中。1 乙個物件 引用或指標的靜態型別決定了該物件的哪些成員是可見的,即使靜態型別與動態型別不一致 當使用基類的引用或指標時,會發生這種情況 2 派生類的成員將隱藏同名的基類成員,使用作用域來使用乙個被隱藏的基類成員 3 名...