阅读:5792回复:0
C++基础知识-继承1. 继承 继承(inherit)是使代码可以复用的重要手段,也是面向对象程序设计的核心思想之一。简单的说,继承是指一个对象直接使用另一对象的属性和方法。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。C++中的继承关系就好比现实生活中的父子关系,继承一笔财产比白手起家要容易得多,原始类称为基类,继承类称为派生类,它们是类似于父亲和儿子的关系,所以也分别叫父类和子类。而子类又可以当成父类,被另外的类继承。 2. 继承方式 继承的方式有三种分别为公有继承(public),保护继承(protect),私有继承(private)。 图片:1.png ![]() 2.1. 公有继承(public) 公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员不能被派生类所访问。 2.2. 保护继承(protected) 保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员不能被派生类所访问。 2.3. 私有继承(private) 私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。基类的私有成员不能被派生类所访问。 2.4. 访问权限 private能够对外部和子类保密,即除了成员所在的类本身可以访问之外,别的都不能直接访问。protected能够对外部保密,但允许子类直接访问这些成员。public、private和protected对成员数据或成员函数的保护程度可以用下表来描述: [table][tr][td=1,1,123]
基类的 public成员 [/td][td=1,1,142]基类的 protected成员 [/td][td=1,1,132]基类的 private成员 [/td][/tr][tr][td=1,1,123]Public继承 [/td][td=1,1,132]Public属性 [/td][td=1,1,142]Protected属性 [/td][td=1,1,132]不可见 [/td][/tr][tr][td=1,1,123]Protected继承 [/td][td=1,1,132]Protected属性 [/td][td=1,1,142]Protected属性 [/td][td=1,1,132]不可见 [/td][/tr][tr][td=1,1,123]Private继承 [/td][td=1,1,132]Private属性 [/td][td=1,1,142]Private属性 [/td][td=1,1,132]不可见 [/td][/tr][/table]2.5. 示例 例如有一个基类是如下声明定义的:[table][tr][td=1,1,553] class Base { public: void publicFun() {cout<<"publicFun"<<endl;} int m_nPublicData; protected: void protectedFun() {cout<<"protectedFun"<<endl;} int m_nProtectedData; private: void privateFun() {cout<<"privateFun"<<endl;} int m_nPrivateData; };[/td][/tr][/table] 2.5.1. public继承示例 [table][tr][td=1,1,553] class PublicDerive : public Base { public: void test() { publicFun(); protectedFun(); privateFun(); // error: 'void Base::privateFun()' is private m_nPublicData = 1; m_nProtectedData = 2; m_nPrivateData = 3; // error: 'int Base::m_nPrivateData' is private } };[/td][/tr][/table] 从派生类内部调用方面来看,公有继承,派生类能够使用的父类非私有属性的成员。[table][tr][td=1,1,553] void callFun() // 三种类成员访问限定符示例 { … PublicDerive publicD; publicD.publicFun(); publicD.m_nPublicData=1; base.protectedFun(); // error: 'void Base::protectedFun()' is protected base.m_nProtectedData=2; // error … }[/td][/tr][/table] 从派生类外部调用方面来看,公有继承,继承得到的公有成员具有公有属性;保护成员具有保护属性(只供派生类成员内部或友元访问)。 2.5.2. protected继承示例 [table][tr][td=1,1,553] class ProtectedDerive : protected Base { public: void test() { publicFun(); protectedFun(); privateFun(); // error: 'void Base::privateFun()' is private m_nPublicData = 1; m_nProtectedData = 2; m_nPrivateData = 3; // error: 'int Base::m_nPrivateData' is private } };[/td][/tr][/table] 从派生类内部调用方面来看,保护继承,派生类能够使用的父类非私有属性的成员。[table][tr][td=1,1,553] void callFun() // 三种类成员访问限定符示例 { … ProtectedDerive protectedD; protectedD.publicFun(); // error: 'Base' is not an accessible base of 'ProtectedDerive' protectedD.m_nPublicData=1; // error … }[/td][/tr][/table] 从派生类外部调用方面来看,保护继承,继承得到的公有成员、保护成员具有保护属性(只供派生类成员内部或友元访问)。 2.5.3. private继承示例 [table][tr][td=1,1,553] class PrivateDerive : private Base { public: void test() { publicFun(); protectedFun(); privateFun(); // error: 'void Base::privateFun()' is private m_nPublicData = 1; m_nProtectedData = 2; m_nPrivateData = 3; // error: 'int Base::m_nPrivateData' is private } };[/td][/tr][/table] 从派生类内部调用方面来看,私有继承,派生类能够使用的父类非私有属性的成员。[table][tr][td=1,1,553] void callFun() // 三种类成员访问限定符示例 { PrivateDerive privateD; privateD.publicFun(); // error: 'Base' is not an accessible base of 'PrivateDerive' privateD.m_nPublicData=1; // error }[/td][/tr][/table] 从派生类外部调用方面来看,私有继承,继承得到的公有成员、保护成员具有私有属性。 3. 多继承 多继承指一个派生类有多个基类。如图3-1所示: 图片:2.jpg ![]() 图3-1 多继承 [table][tr][td=1,1,553]class A { public: A()
{cout<<"A()"<<endl;} int dataA; }; class B { public: B()
{cout<<"B()"<<endl;} int dataB; }; class C : public A,
public B { public: C()
{cout<<"C()"<<endl;} int dataC; }; [/td][/tr][/table]3.1. 内存分布 3.1.1. 非静态成员 根据图3-1,A中有一个int型变量dataA;B中有一个int型变量dataB;C继承于A,B,且C中包含一个int型变量。 如果分别对类A,B,C进行sizeof。可得:[table][tr][td=1,1,553] cout<<"sizeof(A)"<<sizeof(A)<<endl; // 4 cout<<"sizeof(B)"<<sizeof(B)<<endl; // 4 cout<<"sizeof(C)"<<sizeof(C)<<endl; // 12[/td][/tr][/table] 由此可知,C中包含了数据dataA,dataB,dataC 3.1.2. 静态成员 如果在基类中声明了静态数据,对派生类的大小无影响。测试如下:[table][tr][td=1,1,553] // 修改类A,添加静态数据 class A { public: ... static int staticData; // 静态数据 }; // 测试 cout<<"sizeof(A)"<<sizeof(A)<<endl; // 4[/td][/tr][/table] 3.2. 构造次序 按照C++的语法,在创建一个类时,都会调用类的构造函数;当创建一个派生类C时,则先调用基类的构造函数,再调用自己(派生类)。在多继承中,基类的构造顺序是根据派生类中的继承顺序而来的。例如:[table][tr][td=1,1,553] class C : public A,
public B {……}; [/td][/tr][/table]
构造顺序,则是,先A,然后B,最后C。 如果继承顺序是:[table][tr][td=1,1,553] class C : public B,
public A {……};[/td][/tr][/table] 构造顺序,则是,先B,然后A,最后C。 在继承关系中根据C++的语法,在创建派生类对象时会先默认调用基类的默认构造函数(无参构造函数)。如果基类没有定义默认构造函数(即手动定义了构造函数,系统则不会定义默认的构造函数)时,而定义的构造函数定义了多个时,如果没有在派生类的初始化列表中指明使用哪个基类的构造函数时,系统先去查看是否有定义无参构造函数,如有,则调用无参构造函数;没有,则编译失败。示例如下:[table][tr][td=1,1,553] class A { public: A() {cout<<"A()"<<endl;} int dataA; }; class B { public: B(int d1, int d2) {cout<<"B(int d1, int d2)"<<endl;} B() {cout<<"B()"<<endl;} B(int d) {cout<<"B(int d)"<<endl;} int dataB; }; class C : public A, public B { public: C():B(2) {cout<<"C()"<<endl;} int dataC; };[/td][/tr][/table] 在基类B中,有3个构造函数。在派生类C中,C的构造函数中的初始化列表使用了B(2),即调用的的B(int d)。测试结果如下:[table][tr][td=1,1,553] A() B(int d) C()[/td][/tr][/table] 如果在C中不指名使用哪个构造函数,修改如下:[table][tr][td=1,1,553] class C : public A, public B { public: C()/*:B(2) */{cout<<"C()"<<endl;} int dataC; };[/td][/tr][/table] 输出结果如下:[table][tr][td=1,1,553] A() B() C()[/td][/tr][/table] 如果注释掉无参构造,修改如下:[table][tr][td=1,1,553] class B { public: B(int d1, int d2) {cout<<"B(int d1, int d2)"<<endl;} // B() {cout<<"B()"<<endl;} B(int d) {cout<<"B(int d)"<<endl;} int dataB; };[/td][/tr][/table] 测试结果如下:[table][tr][td=1,1,553] error: no matching function for call to 'B::B()'[/td][/tr][/table] 析构函数的调用顺序和构造函数的顺序相反。 3.3. 二义性 由3.1可知,派生类中的成员会继承基类的成员,在多继承中,多个基类有同名的函数,派生类都会继承下来。但是,当调用时,无法确定需要调用的是哪个基类的函数,这就产生了二义性。 修改类A,类B,添加同名函数sameNameFun,示例如下:[table][tr][td=1,1,553] class A { public: A() {cout<<"A()"<<endl;} void sameNameFun() {cout<<"A::sameNameFun()"<<endl;} int dataA; }; class B { public: B(int d1, int d2) {cout<<"B(int d1, int d2)"<<endl;} B() {cout<<"B()"<<endl;} B(int d) {cout<<"B(int d)"<<endl;} void sameNameFun() {cout<<"B::sameNameFun()"<<endl;} int dataB; };[/td][/tr][/table] 当创建C的对象,并调用函数sameNameFun(),则会报错:[table][tr][td=1,1,553] // 调用 C c; c. sameNameFun(); // 报错结果 error: request for member 'sameNameFun' is ambiguous c.sameNameFun();[/td][/tr][/table] 在多继承中解决二义性有两种方式:类名限定,同名覆盖。(多重继承中采用虚基类继承) 3.3.1. 类名限定 类名限定,就是在调用函数前加上类名以及作用域限定符。假如需要调用A的sameNameFun,示例如下:[table][tr][td=1,1,553] // 调用 C c; c.A::sameNameFun(); // 类名限定 // 输出结果 A::sameNameFun()[/td][/tr][/table] 3.3.2. 同名覆盖 同名覆盖,指在派生类中定义同名函数,且参数表必须和基类的保持一致。示例如下:[table][tr][td=1,1,553] class C : public A, public B { public: C():B(2) {cout<<"C()"<<endl;} void sameNameFun() // 同名覆盖 { cout<<"C::sameNameFun()"<<endl; } int dataC; };[/td][/tr][/table] 测试如下:[table][tr][td=1,1,553] // 调用 C c; c.sameNameFun(); // 输出结果 C::sameNameFun()[/td][/tr][/table] 如果想要调用基类的函数,则加上作用域限定符即可。 4. 多重继承 多重继承,即基类A、派生类B(继承父类A)、派生类C(C继承父类B)。 这里说明的是,基类A中的数据成员(前提:公有继承方式,)会被派生类B所继承,隐式的存放在派生类B的public下,此时派生类C公有继承父类B,C中则包含从B和A中的公有数据成员和函数。示意图如下: 图片:3.jpg ![]() 4.1. 砖石问题(菱形继承) 如下图所示,表示了一个砖石问题。基类Furniture(家具),其派生类Sofa(沙发)和Bed(床),而SofaBed(沙发床)则继承于Sofa和Bed,继承关系都是公有继承。在基类Furniture中有变量length,函数baseFun()。 图片:4.jpg ![]() 示例代码如下:
由输出结果可知,SofaBed无法确定调用的length是Sofa,还是Bed中的,所以具有二义性。 解决办法参考下一节,虚继承。 5. 虚继承 注:若非特别需求设计,尽量不要使用虚继承。 虚继承是多重继承中特有的概念。虚基类是为解决多重继承而出现的。如:类D继承自类B1、B2,而类B1、B2都继承自类A,因此在类D中两次出现类A中的变量和函数。为了节省内存空间,可以将B1、B2对A的继承定义为虚继承,而A就成了虚基类。实现的代码如下:
5.1. 虚基类声明 在派生类继承基类的顺序表中的访问控制符前或后添加了关键字“virtual”,我们就说,派生类进行了虚基类的声明。示例如下:
当虚继承了一个基类,派生类则会创建一个虚基类表指针。 5.2. 虚基类表指针 虚基类表指针,派生类虚继承一个基类而来。 在处于多重继承中的另类表示时,每一个分支使用一个虚基类指针。测试如下:
如果CommonBase中的添加成员变量,其测试结果如下,此方法便是解决砖石问题的方案。
5.3. 虚函数表指针 在一个类中的成员函数前,添加一个关键字“virtual”,则这个函数为虚函数。虚函数是实现多态的关键,在此不讨论这个。类中有了虚函数,这个类就会创建一个虚函数表指针。 注:虚基类表指针,和虚函数表指针是不同的指针 参考: [1] https://www.cnblogs.com/33debug/p/6666939.html C++中的继承(1) 三种继承方式 [2] http://blog.chinaunix.net/uid-26722078-id-3484671.html详解C++多重继承 [3] https://blog.csdn.net/xiaoyuxianshenging/article/details/60767643 [4] http://www.cnblogs.com/BeyondAnyTime/archive/2012/06/05/2537451.html 关于C++中的虚拟继承的一些总结 [5] http://blog.chinaunix.net/uid-26722078-id-3484674.html 详解C++虚拟继承 [6] https://blog.csdn.net/chengonghao/article/details/51701290 C++ 虚继承的对象模型 [7] https://blog.csdn.net/yuanchunsi/article/details/78697416 C++ 深入理解 虚继承、多重继承和直接继承 |
||||||
最新喜欢:![]() |