前面講了封裝,但封裝只是隱藏了類內部實現。若是使用多態隱藏類自己的話,只有封裝是不夠的,還須要繼承。數據結構
經過封裝。咱們把一些相關的函數和變量包裹在了一塊兒,這些函數和變量就叫作類的成員函數和成員變量。繼承就是一種獲取這個類的成員函數和成員變量的方式。一般,繼承了某個類的類叫作該類的派生類或者子類。函數
根據封裝的意義,當父類將部分紅員函數和成員變量的訪問權限設置爲private時,即便被繼承了,子類仍然沒法訪問。this
下面經過例子來講明一下,子類是如何保存這些繼承來的成員函數和成員變量的。指針
class A { public: A() : a( 1 ) { } void foo() { std::cout << "A::foo()" << std::endl; return; } private: int a; }; class B : public A { public: B() : b( 2 ) { } private: int b; }; int main() { B x; x.foo(); return 0; }
繼承能夠是public、protected或private,不一樣關鍵字爲父類設置了不一樣訪問權限。調試
- public繼承意味着父類全部成員成員變量的訪問權限在子類中維持不變
- protected繼承意味着父類中public的成員函數和成員變量在子類中變爲protected
- private繼承意味着父類中public和protected的成員函數和成員變量在子類中變爲private
由於類成員函數能夠經過隱式參數this區分具體的調用對象,因此類成員函數只須要存在一份就能夠。當子類繼承父類的成員函數時,子類只是獲得了經過子類對象訪問父類成員函數的權利。code
隱式參數意味着你沒有寫,可是編譯器幫你寫了。對象
當咱們gdb調試上面的代碼時,會發現x.foo()
實際調用的就是A::foo()
,而不是A::foo()
的一份拷貝。繼承
0x00000000004004fe <+8>: lea -0x10(%rbp),%rax # -0x10(%rbp)就是x的地址 0x0000000000400502 <+12>: mov %rax,%rdi # 將x的地址做爲A::foo()的參數,也就是this 0x0000000000400505 <+15>: callq 0x400512 <A::foo()>
其實任何函數都只須要存在一份,成員函數只是一個稍微特殊的函數。編譯器
虛函數又是一個稍微特殊的成員函數,它也只存在一份,只不過是在調用上可能要多些操做,細節在講多態的時候再說。編譯
每一個類的實例對象都要變動本身的成員變量,所以其空間確定都是獨立的。當子類繼承父類的成員變量時,實際只是繼承了父類的數據結構。
當經過gdb打印x
的值時,咱們會發現它的結構以下
(gdb) p x $1 = {<A> = {a = 1}, b = 2}
當咱們直接查看x
的地址的內容時,會發現A::a
和B::b
就是連續排布的。
(gdb) x /2x &x 0x7fffffffe540: 0x00000001 0x00000002
也就是說當子類繼承父類時,子類的數據結構就是父類的成員變量加上子類自身的成員變量。
之因此父類的成員變量放在前,是由於父類指針能夠直接指向子類,當以父類指針操做父類成員變量時就不須要額外進行地址偏移了。若是子類成員變量在前,那麼父類指針操做時還須要跳過子類成員變量。
咱們大膽推測當子類繼承多個父類時,子類的數據結構就是寫在前的父類的成員變量加上寫在後的父類的成員變量再加上子類自身的成員變量,事實也確實如此。
多繼承時,由於存在多個父類數據結構,因此當不一樣的父類指針指向子類時,會進行必定偏移,保證該父類指針恰好指向本身的數據結構的起始位置。
多繼承會複雜化類關係圖,並且在一些場景下會帶來歧義,所以都不建議使用多繼承。反正我本身到如今爲止都沒在實際項目中用過,只是在一些開源代碼中看到過。
繼承除了是多態的基礎外,仍是一種複用代碼的方式。可是謹記只有存在父子關係時才使用繼承,若是隻是爲了複用代碼的話,應當使用組合。