黄维
论坛版主
论坛版主
  • UID531
  • 粉丝0
  • 关注0
  • 发帖数2
阅读:5792回复:0

C++基础知识-继承

楼主#
更多 发布于:2019-03-26 09:34


1.   继承


继承(inherit)是使代码可以复用的重要手段,也是面向对象程序设计的核心思想之一。简单的说,继承是指一个对象直接使用另一对象的属性和方法。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。C++中的继承关系就好比现实生活中的父子关系,继承一笔财产比白手起家要容易得多,原始类称为基类,继承类称为派生类,它们是类似于父亲和儿子的关系,所以也分别叫父类和子类。而子类又可以当成父类,被另外的类继承。

2.   继承方式


继承的方式有三种分别为公有继承(public,保护继承(protect),私有继承(private)。

图片:1.png



2.1. 公有继承(public)


公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员不能被派生类所访问。

2.2. 保护继承(protected)


保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员不能被派生类所访问。

2.3. 私有继承(private)


私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。基类的私有成员不能被派生类所访问。

2.4. 访问权限


private能够对外部和子类保密,即除了成员所在的类本身可以访问之外,别的都不能直接访问。protected能够对外部保密,但允许子类直接访问这些成员。publicprivateprotected对成员数据或成员函数的保护程度可以用下表来描述:
[table][tr][td=1,1,123]
[/td][td=1,1,132]
基类的
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-1A中有一个int型变量dataAB中有一个int型变量dataBC继承于AB,且C中包含一个int型变量。
如果分别对类ABC进行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中包含了数据dataAdataBdataC

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.    类名限定


类名限定,就是在调用函数前加上类名以及作用域限定符。假如需要调用AsameNameFun,示例如下:[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)、派生类CC继承父类B)。
这里说明的是,基类A中的数据成员(前提:公有继承方式,)会被派生类B所继承,隐式的存放在派生类Bpublic下,此时派生类C公有继承父类BC中则包含从BA中的公有数据成员和函数。示意图如下:

图片:3.jpg



4.1. 砖石问题(菱形继承)


如下图所示,表示了一个砖石问题。基类Furniture(家具),其派生类Sofa(沙发)Bed(),而SofaBed(沙发床)则继承于SofaBed,继承关系都是公有继承。在基类Furniture中有变量length,函数baseFun()

图片:4.jpg


示例代码如下:

class Furniture // 家具
{
public:
 
  Furniture():length(0){cout<<"Furniture()"<<length<<endl;}

 
  void baseFun()
  {cout<<"Furniture::baseFun()"<<endl;}

public:
 
  int length;

};

class Sofa :public Furniture
{
public:
 
  Sofa(){cout<<"sofa()"<<length<<endl;}

};

class Bed :public Furniture{
public:
 
  Bed(){cout<<"Bed()"<<length<<endl;}

};

class SofaBed :public Sofa,public
  Bed{

public :
 
  SofaBed(){}

};
// 创建SofaBed的对象
SofaBed sb1;
sb1.length;
// 输出结果
error: request
  for member 'length' is ambiguous

由输出结果可知,SofaBed无法确定调用的lengthSofa,还是Bed中的,所以具有二义性。
解决办法参考下一节,虚继承。

5.   虚继承


注:若非特别需求设计,尽量不要使用虚继承。
虚继承是多重继承中特有的概念。虚基类是为解决多重继承而出现的。如:D继承自类B1B2,而类B1B2都继承自类A,因此在类D中两次出现类A中的变量和函数。为了节省内存空间,可以将B1B2A的继承定义为虚继承,而A就成了虚基类。实现的代码如下:

class A

class B1:public
  virtual A;


class B2:public
  virtual A;


class D:public
  B1,public B2;

 

5.1. 虚基类声明


在派生类继承基类的顺序表中的访问控制符前或后添加了关键字“virtual”,我们就说,派生类进行了虚基类的声明。示例如下:

class A :
  virtual public Base1{…}; // A虚继承Base1
class A : public
  virtual Base1{…}; // virtual 放在访问符前后都可以

当虚继承了一个基类,派生类则会创建一个虚基类表指针。

5.2. 虚基类表指针


虚基类表指针,派生类虚继承一个基类而来。
在处于多重继承中的另类表示时,每一个分支使用一个虚基类指针。测试如下:

/////////***多重继承(另类表示)***/////////
class CommonBase
{
};
 
class Base11:
  public CommonBase
{
private:
       int b1;
}; // sizeof(Base11)=4
 
class Base22:
  public CommonBase
{
private:
       int b2;
};//
  sizeof(Base22)=4
 
class Derived12
  : virtual public Base11, virtual public Base22
{
private:
       int d;
};//
  sizeof(Derived12)=4(Base11)+4(Base22)+4(自身)+4(虚基类指针)*2=20

如果CommonBase中的添加成员变量,其测试结果如下,此方法便是解决砖石问题的方案。

/////////***多重继承(另类表示)***/////////
class CommonBase
{
       int co;
};
 
class Base11:
  public CommonBase
{
private:
       int b1;
}; //
  sizeof(Base11)=4(自身)+4(CommonBase)=8
 
class Base22:
  public CommonBase
{
private:
       int b2;
};// sizeof(Base22)=4(自身)+4(CommonBase)=8
 
class Derived12
  : virtual public Base11, virtual public Base22
{
private:
       int d;
};//
  sizeof(Derived12)=8(Base11)+8(Base22)+4(自身)+4(虚基类指针)*2-4(Base11Base22继承的双份co,变为一份)=24

 

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++ 深入理解 虚继承、多重继承和直接继承

最新喜欢:

何万里何万里
游客

返回顶部