前面讲了封装,但封装只是隐藏了类内部实现。若是使用多态隐藏类自己的话,只有封装是不够的,还须要继承。数据结构
经过封装。咱们把一些相关的函数和变量包裹在了一块儿,这些函数和变量就叫作类的成员函数和成员变量。继承就是一种获取这个类的成员函数和成员变量的方式。一般,继承了某个类的类叫作该类的派生类或者子类。函数
根据封装的意义,当父类将部分红员函数和成员变量的访问权限设置为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
也就是说当子类继承父类时,子类的数据结构就是父类的成员变量加上子类自身的成员变量。
之因此父类的成员变量放在前,是由于父类指针能够直接指向子类,当以父类指针操做父类成员变量时就不须要额外进行地址偏移了。若是子类成员变量在前,那么父类指针操做时还须要跳过子类成员变量。
咱们大胆推测当子类继承多个父类时,子类的数据结构就是写在前的父类的成员变量加上写在后的父类的成员变量再加上子类自身的成员变量,事实也确实如此。
多继承时,由于存在多个父类数据结构,因此当不一样的父类指针指向子类时,会进行必定偏移,保证该父类指针恰好指向本身的数据结构的起始位置。
多继承会复杂化类关系图,并且在一些场景下会带来歧义,所以都不建议使用多继承。反正我本身到如今为止都没在实际项目中用过,只是在一些开源代码中看到过。
继承除了是多态的基础外,仍是一种复用代码的方式。可是谨记只有存在父子关系时才使用继承,若是只是为了复用代码的话,应当使用组合。