整個C++程序設計全面圍繞面向對象的方式進行。類的繼承特性是C++的一個非常重要的機制。繼承特性可以使一個新類獲得其父類的操作和數據結構,程序員只需在新類中增加原有類沒有的成分。
在面試過程中,各大企業會考量你對虛函數、純虛函數、私有繼承、多重繼承等知識點的掌握程度,因此就有了我們這一節的內容,開始吧。
1、以下代碼的輸出結果是什麼?
|
#include<iostream> using namespace std;
class A { protected : int m_data; public : A( int data = 0) { m_data = data; } int GetData() { return doGetData(); } virtual int doGetData() { return m_data; } }; class B : public
A { protected : int m_data; public : B( int data = 1) { m_data = data; } int doGetData() { return m_data; } }; class C : public
B { protected : int m_data; public : C( int data = 2) { m_data = data; } }; int
main () { C c(10); cout << c.GetData() <<endl; cout << c.A::GetData() <<endl; cout << c.B::GetData() <<endl; cout << c.C::GetData() <<endl; cout << c.doGetData() <<endl; cout << c.A::doGetData() <<endl; cout << c.B::doGetData() <<endl; cout << c.C::doGetData() <<endl; return 0; } |
解析:構造函數從最初始的基類開始構造的,各個類的同名變量沒有形成覆蓋,都是單獨的變量.理解這兩個重要的C++特性後解決這個問題就比較輕鬆了,下面我們看看:
cout << c.GetData() <<endl;
本來是要調用C類的GetData(),C中未定義,故調用B中的,但是B中也未定義,故調用A中的GetData(),因爲A中的doGetData()是虛函數,所以調用B類中的doGetData(),而B類的doGetData()返回B::m_data,故輸出 1。
cout << c.A::GetData() <<endl;
因爲A中的doGetData()是虛函數,所以調用B類中的doGetData(),而B類的doGetData()返回B::m_data,故輸出 1。
cout << c.B::GetData() <<endl;
肯定是B類的返回值 1 了。
cout << c.C::GetData() <<endl;
跟cout << c.GetData() <<endl;語句是一樣的。
cout << c.doGetData() <<endl;
B類的返回值 1 了。
cout << c.A::doGetData() <<endl;
因爲直接調用了A的doGetData() ,所以輸出0。
cout << c.B::doGetData() <<endl;
cout << c.C::doGetData() <<endl;
這兩個都是調用了B的doGetData(),所以輸出 1。
這裏要注意存在一個就近調用,如果父類存在相關接口則優先調用父類接口,如果父類也不存在相關接口則調用祖父輩接口。
答案:
1 1 1 1 1 0 1 1
2、以下代碼輸出結果是什麼?
#include<iostream> using namespace std; class A { public : void virtual f() { cout<< "A" <<endl; } }; class B : public
A { public : void virtual f() { cout<< "B" <<endl; } }; int
main () { A* pa= new A(); pa->f(); B* pb=(B*)pa; pb->f(); delete pa,pb; pa= new B(); pa->f(); pb=(B*)pa; pb->f(); return 0; } |
解析:這是一個虛函數覆蓋虛函數的問題。A類裏的f()函數是一個虛函數,虛函數是被子類同名函數所覆蓋的。而B類裏的f()函數也是一個虛函數,它覆蓋A類f()函數的同時,也會被它的子類覆蓋。但是在 B* pb=(B*)pa;裏面,該語句的意思是轉化pa爲B類型並新建一個指針pb,將pa複製到pb。但是這裏有一點請注意,就是pa的指針始終沒有發生變化,所以pb也指向pa的f()函數。這裏並不存在覆蓋的問題。
delete pa,pb;刪除了pa和pb所指向的地址,但是pa、pb指針並沒有刪除,也就是我們通常說的懸浮指針,現在重新給pa指向新地址,所指向的位置是B類的,而之前pa指針類型是A類的,所以就產生了一個覆蓋。pa->f();的值是B。
pb=(B*)pa;轉化pa爲B類指針給pb賦值,但pa所指向的f()函數是B類的f() 函數,所以pb所指向的f()函數是B類的f()函數。pb->f();的值是B。
答案:
A A B B
3、派生類的3種繼承方式?
答案:
(1)公有繼承方式:
基類成員對其對象的可見性與一般類及其對象的可見性相同,公有成員可見,其他成員不可見。這裏保護成員與私有成員相同。
基類成員對派生類的可見性對派生類來說,基類的公有成員和保護成員可見,基類的公有成員和保護成員作爲派生類的成員時,它們都保持原有的狀態;基類的私有成員不可見,基類的私有成員仍然是私有的,派生類不可訪問基類中的私有成員。
基類成員對派生類對象的可見性對派生類對象來說,基類的公有成員是可見的,其他成員是不可見的。
(2)私有繼承方式:
基類成員對其對象的可見性與一般類及其對象的可見性相同,公有成員可見,其他成員不可見。
基類成員對派生類的可見性對派生類來說,基類的公有成員和保護成員可見,基類的公有成員和保護成員都作爲派生類的私有成員,並且不能被這個派生類的子類所訪問;基類的私有成員不可見,派生類不可訪問基類中的私有成員。
基類成員對派生類對象的可見性對派生類對象來說,基類的所以成員都是不可見的。
所以說,在私有繼承時,基類的成員只能由直接派生類訪問,而無法再往下繼承。
(3)保護繼承方式:
這種繼承方式與私有繼承方式的情況相同,兩者的區別僅在於對派生類的成員而言,基類成員對其對象的可見性與一般類及其對象的可見性相同,公有成員可見,其他成員不可見。
基類成員對派生類的可見性對派生類來說,基類的公有成員和保護成員可見,基類的公有成員和保護成員都作爲派生類的保護成員,並且不能被這個派生類的子類所訪問;基類的私有成員不可見,派生類不可訪問基類中的私有成員。
基類成員對派生類對象的可見性對派生類對象來說,基類的所以成員都是不可見的。
所以說,在私有繼承時,基類的成員只能由直接派生類訪問,而無法再往下繼承。
4、下面程序運行結果是什麼?
#include<iostream> using namespace std; class A { char k[3]; public : virtual void aa(){}; }; class B : public
virtual A { char j[3]; public : virtual void bb(){}; }; class C : public
virtual B { char i[3]; public : virtual void cc(){}; }; int
main () { cout << "sizeof(A):" <<
sizeof (A) << endl; cout << "sizeof(B):" <<
sizeof (B) << endl; cout << "sizeof(C):" <<
sizeof (C) << endl; return 0; } |
解析:(1)對於A類,由於有一個虛函數,那麼必須有一個對應的虛函數表來記錄對應的函數入口地址。每個地址需標有一個虛指針,指針的大小爲4。類中還有一個char k[3],每一個char值所佔空間是1,所以char k[3]所佔大小是3。做一個數據對齊後變爲4。所以,sizeof(A)的結果就是char k[3]所佔大小4和虛指針所佔大小4之和等於8。
(2)對於B類,由於B繼承了A,同時還擁有自己的虛函數,那麼B中首先擁有一個vfptr_B,指向自己的虛函數表。還有char j[3],大小爲4,可虛繼承該如何實現呢?首先要通過加入一個虛類指針(記vbptr_B_A)來指向其父類,然後還要包含父類的所有內容,所以sizeof(B)的大小是:A類所佔大小8,char j[3]所佔大小4,vfptr_B所佔大小4,vbptr_B_A所佔大小4,它們之和等於20。
(3)對於C類和B類差不多,結果是32。
答案:
sizeof(A):8
sizeof(B):20
sizeof(C):32
5、什麼是虛繼承?它與一般的繼承有什麼不同?它有什麼用?寫出一段虛繼承的C++代碼。
答案:
虛擬繼承是多重繼承中特有的概念。虛擬基類是爲了解決多重繼承而出現的,請看下圖:
在圖 1中,類D接觸自類B和類C,而類B和類C都繼承自類A,因此出現了圖 2所示的情況。
在圖 2中,類D中會出現兩次A。爲了節省內存空間,可以將B、C對A的繼承定義爲虛擬繼承,而A成了虛擬基類。最後形成了圖 3。
代碼如下:
1234class
A;
class
B :
public
virtual
A;
class
C :
public
virtual
A;
class
D :
public
B,
public
C;