C 基礎 類繼承時的作用域巢狀

2021-10-06 15:11:01 字數 4364 閱讀 2351

引言

在繼承情況下,派生類的作用域巢狀在基類作用域中:如果不能在派生類作用域中確定名字,就在外圍基類作用域中查詢該名字的定義。

正是這種類作用域的層次巢狀使我們能夠直接訪問基類的成員,就好像這些成員是派生類成員一樣:

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 名...