武林網訊 如果兩個類,Parent和Child,Child繼承Parent,Parent有一個函數print(),有成員變量pVar,Child中有兩個函數print(),privatePrint(),有成員變量cVar,
現在 ,申明一個Parent類型的指針來指向一個Child的對象。
那么用這個指針可以調用privatePrint()函數嗎?可以用這個指針可以調用成員cVar嗎?
答案是:不能。
那如果Parent中有一個虛函數privatePrint(),那么可以調用到privatePrint()嗎?
答案是:可以。
首先我用C++試了一遍。C++的代碼如下
代碼
1 #include <iostream>
2
3 using namespace std;
4
5 class Prarent
6 {
7 public:
8 Prarent(){};
9 int pVar;
10 void print()
11 {
12 cout<<"I am parent"<< endl;
13 }
14 //virtual void privatePrint(){};
15 };
16
17 class Child : public Prarent
18 {
19 public:
20 Child(){};
21 int cVar;
22 void print()
23 {
24 cout<<"I am child"<< endl;
25 }
26 void privatePrint()
27 {
28 cout<<"I am child' function privatePrint()"<< endl;
29 }
30 };
31
32 void main()
33 {
34 Prarent *parent;
35 Child child; // 原先我竟然寫成 Child child = new Child(); 用久了C#, 都是C#惹的禍.
36 parent = &child;
37 parent->print();
38 // parent->privatePrint(); 編譯通不過.
39 // 1: 說明父類的指針雖然指向了子類的對象, 但是卻不能引用父類沒有申明的函數.
40 // 2: 但是你將privatePrint申明為父類的虛函數, 則上面的可以運行成功.
41 }
42
43
其實之前我沒有怎么在意這個問題。知道My sen Brother問了我。我才發現問題并非簡單。
經過了我們相互的討論之后,我們得到了一種解釋。
我不敢保證一定完全是對的。但是卻是我們自己的心得和體會。
理解這個問題。首先要明白兩個問題:1:類在內存是怎么存放的?(編譯階段實現)2:對象實例在內存中是怎么存放的?
1:類在內存是怎么存放的?
我根據以前Teacher Lei說過的一些內容,計算機組成,匯編語言,自己看過一些書,得到自己的一種思考。
其中一個類,通過編譯分別存在內存的兩塊地方,一個是代碼段,一個是數據段;
代碼段存放一個類的成員變量;(ie. 上面的pVar和cVar都是存放在代碼段中)
數據段存放一個類的成員函數表;(ie. 上面的print()和privatePrint()都存放于這里);
數據段中的每一個類的內存塊應該由兩個表填充,一個為虛函數表,一個為非虛函數表;
數據段中類的成員變量存放,如下:
int pVar
int cVar
代碼段中類的成員函數表存放,如下:
Parent()
Print()
Child()
Print()
pravtePrint()
下面來解釋 2:對象實例在內存中是怎么存放的?
我們拿上面的例子來說明:
當初始化一個Parent *parent 一個指針對象時,這時候parent所指向的地址就是100;
vPtr(虛函數指針) 地址:100
int pVar
當初始化一個Child類型 child 對象時,這時候child的地址就是200;(注意和指針不一樣)
vPtr(虛函數指針) 地址:200
int pVar
int cVar
首先:為什么這邊一定是這樣排列的
還記得Teacher Lei說過嗎?原因:子類在初始化的時候是先調用父類的構造函數!!
說明父類的成員一定是先被初始化的。
所以這邊的結構必然是這樣的。(這里很重要)
好了。到現在基本就把要知道的基礎知識解決了。
不知道讀著博客的人累了沒有。。呵呵。后面的更精彩。
現在 把child對象的地址賦值給指針parent(即 parent = &child)
先來看看 parent->cVar 為什么不行?!
第一步:parent->cVar 其實是一個地址的偏移過程,現在parent的地址是200;那么cVar就代表2個偏移量;
按說電腦是可以找到202的這個地址的值。可是為什么不行呢?
第二步:在程序的編譯過程中,每一個的成員函數名都被當做一個偏移量。
就像這里,pVar代表量1個偏移。cVar代表2個偏移量。
第三步:parent是Parent類型的。這個很關鍵。因為在編譯的以后,parent已經初始化了一個最大偏移量max (這里的max為1)
第四步:結果已經很明顯了。因為parent的最大偏移量max 為1。程序根本找不著偏移量為2的變量地址。
然后看看 parent->privatePrint() 為什么不行?
還是那個關鍵點。parent是Parent類型的,還記得上面說類在內存中的加載方式嗎?parent指向的是Parent的內存塊。所以在那個內存塊中,根本找不著privatePrint()這個函數。
可是?有人因為會問了?為什么如果在Parent中申明了一個虛函數類型的privatePrint()就可以了呢?
還記得類的實例在內存中加載的那個圖嗎?每個類的前面都有一個的vPtr虛函數指針,他指向的是當前類的虛函數表。通過虛函數表中的privatePrint()也相當一個指針,指向了子類中實現父類虛函數的privatePrint(),自然就能找到了。
新聞熱點
疑難解答