一般情況下來說,父類的中的成員往往是最委屈的,子類要是有個同名(僅僅需要名字一樣
)成員,自己的成員就會被隱藏,子類還可以訪問自己的非私有成員,但是自己想訪問子類中的成員就不行,當子類與自己有同名的成員時,訪問到的是自己的成員,沒有同名成員時意圖直接訪問子類成員則直接報錯
.......
這是何等悲慘,為此,我們應該做些什麼來為父類謀得一些權利,讓父類可以順利的訪問子類非費私有成員。
為了讓這一切順利進行,我們先來看看為什麼父類是如此的委屈。這要從c++編譯器對名字的查詢規則說起:
1、編譯器首先在名字所在的**塊
(一對大括號
{})內查詢名字
(名字需要是在使用之前的名字宣告)
2、如果沒找到則繼續查詢外層的作用域,如果找到則立即停止查詢
3、一直向外層查詢且沒找到則報錯 如:
#include int a = 0;
int main()
return 0;
}
輸出結果是 a = 1;
編譯器先在a使用的**塊內查詢使用之前的名字宣告,找到了
if之上一行的名字宣告,並立即停止對名字
a的查詢,並輸出其初始值1
這一規則對應到類中:
1、首先在成員函式體範圍內查詢函式體中使用的名字宣告(其實也就是在名字所在**段)
2、如果沒找到則在類中繼續查詢(類本身就是乙個作用域,類成員屬於類作用域之內,所以相當於在外層作用域中查詢,由於類中宣告部分先於定義被編譯器處理,編譯到函式體時,類中所有名字就已經可見了
),找到則停止查詢
3、繼續向外層作用域查詢,直到找不到並報錯
如:
#include class test
private:
int a = 0; //類內初始化
};int main()
建議用c++11方式編譯, 輸出結果為
a = 0
編譯器先在a使用的**塊內查詢使用之前的名字宣告
,由於該**塊內並沒有關於名字
a的宣告,於是開始向外層的類作用域查詢,又由於類內部成員的宣告是先於定義被處理,即此時成員名字已經可見,於是找到了類末尾的名字宣告。輸出其類內初始值
0上面的名字查詢規則是父類為什麼這麼委屈的原因之一,另外,在遇到繼承時會發生更加奇妙的事情:
發生繼承時,子類的作用域會被巢狀在父類的作用域中!
這導致了子類有機會使用父類的成員:當子類中出現了乙個子類中不存在名字時,編譯器向外層查詢並在父類作用域中查詢到,停止查詢,之後編譯器對名字進行訪問屬性檢查
(注意類的作用域是作用域,類的訪問屬性是訪問屬性,不是同乙個概念,名字在其作用域之內都是可見、可訪問的,訪問屬性是類對外界可見名字的成員訪問許可權說明
),如果名字不是私有屬性,子類使用名字成功,否則失敗,編譯器報錯。 如:
#include class base
;class child : public base
};int main()
編譯報錯,錯誤資訊如下:
編譯器在使用a的**塊內未能找到名字宣告,一直向外查詢直到父類作用域,找到
a的名字宣告,但是在檢查其訪問屬性時發現其為私有屬性,報錯
靜態型別決定名字應該去哪個作用域中尋找
也就是,如果是是以子類物件/引用
/指標去訪問乙個成員,編譯器會在子類的作用域範圍內開始查詢這個名字,如果找到且名字立即停止查詢,再檢查訪問屬性,如果子類作用域範圍沒有找到才會去父類作用域以同樣的方式查詢。這一過程說明了為何會發生同名隱藏。
名字查詢先於型別檢查
這一過程是這樣的,如果名字找到了,編譯器才會開始進行型別檢查,如果型別完全一致,則編譯通過,如果型別不完全一致則嘗試隱式型別轉換,如果轉換成功,則編譯通過,失敗則編譯報錯,因此型別不同時亦可發生同名隱藏。
如:
#include class base
};class child : public base
};int main()
輸出child::a = 1
編譯器發現child的型別為
child
,於是去類
child
的作用域中查詢
,查詢到了
後立即停止查詢並開始進行型別檢查,發現實參型別為
double
,形參型別為
int,但可以隱式轉換,於是沒有報錯
(如果對同名隱藏規則不熟悉,咋一看確實以為會輸出
base::a = 0)。
在函式體child::print內部時,向外查詢到
child::a
時,停止查詢,而不會查詢到子類可訪問的屬性為
protected
的base::a
,因此執行時會輸出
child::a
的類內初始值1。
函式base::print會查詢到
base::a
而不會查詢的
public
的child::a。
是時候為父類做點事情了,讓父類也有訪問子類成員的機會!名字是以什麼樣的方式查詢這個是無法改變的,那怎樣才能有機會在父類中訪問到子類中的成員呢?是時候搬出物件導向三大特徵之一的」多型君」了。
應該這樣定義父類:
class base
virtual void visit_child() = 0;
virtual ~base() noexcept
};
想在父類的成員函式want_to_visit_child訪問子類成員,我們是這樣做的,設計乙個訪問子類的」介面」visit_child,並將其宣告為純虛函式,迫使子類重寫並實現
visit_child
函式體,並在實現的函式體中訪問子類的成員。
子類應該這樣寫:
class child : public base
void name()
};
在子類中,我們重寫了visit_child並在其函式體中訪問了子類的成員變數。
使用是醬嬸的:
int main()
嗯.....結果看上去很好,但是你發現什麼問題了嗎?
按照剛才的名字查詢規則,want_to_visit_child只能訪問父類中的
visit_child
才對啊,為什麼輸出卻顯示他呼叫了子類中的
visit_child
呢?難道是多型?那麼多型是如何發生的呢?不是要通過基類的指標或者引用呼叫虛函式才會發生多型嗎?這到底是怎麼一回事?
把父類的成員函式want_to_visit_child這樣寫,也許你就會發現答案:
void want_to_visit_child()
別忘了非靜態成員函式都有乙個隱藏的this指標引數,通過rtti機制,我們發現了
this
指標的靜態型別是
base
型別,指向了子類物件
child
,並呼叫了虛函式
visit_child
。也就是說,多型了!多型了
!多型了!
成員函式want_to_visit_child擁有乙個隱藏引數
base
型別的this
指標,當在
want_to_visit_child
內部訪問成員時,相當於通過此指標去訪問成員,然而,此時通過
this
訪問的visit_child
又恰好是個虛函式,更巧的是
this
此時正好又指向了子類物件且虛函式確實發生了重寫,於是編譯器通過子類物件中隱藏的虛函式指標訪問虛函式表以查詢應該呼叫的函式位址,這個位址正是子類中重寫的
visit_child
的函式位址,於是多型了!多型了
!多型了!
注意包含標頭檔案
#include #include
參考:c++ primer 第五版 子類繼承父類成員問題
子類不能從父類繼承的有 1.建構函式 2.拷貝建構函式 3.析構函式 子類能從父類繼承的有 1 靜態成員變數 2 靜態成員函式 3 友元函式 4 賦值操作符 過載函式 而private型別成員是可以被子類繼承的,只不過子類不能直接訪問,需要相應的set get函式。class father clas...
Java中關於子類成員變數與父類成員變數同名
重寫和過載是針對方法的,子類的變數可以覆蓋父類的變數,但是不能改變父類的變數。class animals class dogg extends animals public class testduotai 列印結果 animals enjoy 10dog enjoy 20dog enjoy 102...
Java中關於子類成員變數與父類成員變數同名
重寫和過載是針對方法的,子類的變數可以覆蓋父類的變數,但是不能改變父類的變數。class animals int age 10 class dogg extends animals int age 20 animals a new animals system.out.println a.age d...