类的继承

  • 类的继承:一个新类从已有的类那里获得其已有特性
  • 类的派生:从已有的类(父类)产生一个新的子类

类的继承是用已有的类来建立专用类的编程技术。

  • 单继承(single inheritance):一个派生类只从一个基类派生(树形结构)
    在这里插入图片描述
    这一系列文章约定,箭头表示继承的方向,从派生类指向基类。

  • 多重继承(multiple inheritance):一个派生类有两个或多个基类

派生类是基类的具体化,而基类则是派生类的抽象。

  • 派生类的声明方式
class 派生类名: [继承方式默认为`private`] 基类名
{
派生类新增加的成员
} ;
  • 继承方式包括:

    • public(公用的)
    • private(私有的)
    • protected(受保护的):不能被外界(类外不能访问)引用,但可以被派生类的成员引用
  • 派生类的构成

    • 从基类继承来的成员
    • 声明派生类时增加的部分
  • 构造一个派生类

    • 从基类接收成员,派生类把基类全部的成员(不包括构造函数和析构函数)接收过来。
    • 调整从基类接收的成员
    • 声明派生类时增加的成员,一般还应当自己定义派生类的构造函数和析构函数,体现了派生类对基类功能的扩展。
  • 派生类是抽象基类的具体实现

    • 基类中只提供某些最基本的功能,声明派生类时加入某些具体的功能,形成适用于某一特定应用的派生类。这是对基类声明的延续,将一个抽象的基类转化成具体的派生类。
  • 派生类成员的访问属性

    • 基类的成员函数访问基类成员。 //可以
    • 派生类的成员函数访问派生类自己增加的成员。 //可以
    • 基类的成员函数访问派生类的成员。 //不可以
    • 派生类的成员函数访问基类的成员。 ???
    • 在派生类外访问派生类的成员。 //可以访问派生类的公用成员,而不能访问派生类的私有成员。
    • 在派生类外访问基类的成员。// ???
  • 对于(4)和(6),由下面两个因素共同决定基类成员在派生类中的访问属性。

    • 对基类成员所声明的访问属性
    • 派生类所声明的对基类的继承方式
继承方式/访问属性publicprotectedprivate
publicpublicprotected基类private(派生类不可访问)
protectedprotectedprotected基类私有成员
privateprivate成员private成员基类私有成员
  • 基类私有成员:只有基类的成员函数可以引用它,而不能被派生类的成员函数引用,因此就成为派生类中的不可访问的成员。

  • 由上表可以总结出:派生类中,成员有4种不同的访问属性:
    公用的,派生类内和派生类外都可以访问。
    受保护的,派生类内可以访问,派生类外不能访问,其下一层的派生类可以访问。
    私有的,派生类内可以访问,派生类外不能访问。
    不可访问的,派生类内和派生类外都不能访问。

  • 派生类的构造函数

    • 对派生类所增加的数据成员的初始化
    • 对基类的数据成员初始化
    • 对子对象数据成员初始化;
  • 子对象数据成员:类的数据成员中还可以包含类对象,即对象中的对象。

希望在执行派生类的构造函数时,使派生类的数据成员和基类的数据成员同时都被初始化。解决这个问题的思路是: 在执行派生类的构造函数时,调用基类的构造函数。

  • 可以利用初始化表对构造函数的数据成员初始化
  • 可以利用初始化表调用派生类的基类构造函数,实现对基类数据成员的初始化
  • 可以在同一个构造函数的定义中同时实现这两种功能
 派生类构造函数名(成员参数表列):成员1(参数表列初始值),成员2(参数表列初始值)...{}

派生类构造函数名(总参数表列): 基类构造函数名(参数表列)
  {派生类中新增数据成员初始化语句}

 派生类构造函数名(总参数表列): 基类构造函数名(参数表列),成员1(参数表列初始值),成员2(参数表列初始值)...{}

 派生类构造函数名(总参数表列): 基类构造函数名(参数表列),子对象名(参数表列)
  {派生类中新增数成员据成员初始化语句}

  • 在建立一个对象时,执行构造函数的顺序是(析构的顺序相反,先派生,后基类):
    ① 派生类构造函数先调用基类构造函数
    调用子对象构造函数,对子对象数据成员初始化;
    ③ 再执行派生类构造函数本身,对派生类数据成员初始化。

基类构造函数和子对象的次序可以是任意的,编译系统是根据相同的参数名(而不是根据参数的顺序)来确立它们的传递关系的。

多层派生,基类和两个派生类的构造函数的写法: 
基类的构造函数首部: 
Student(int n, string nam)
派生类Student1的构造函数首部: 
Student1(int n, string nam,int a):Student(n,nam)      
派生类Student2的构造函数首部: 
Student2(int n, string nam,int a,int s):Student1(n,nam,a)
在声明Student2类对象时,调用Student2构造函数;在执行Student2构造函数时,先调用Student1构造函数;在执行Student1构造函数时,先调用基类Student构造函数。
  • 派生类的析构函数

调用的顺序与构造函数正好相反: 先执行派生类自己的析构函数,对派生类新增加的成员进行清理,然后调用子对象的析构函数,对子对象进行清理,最后调用基类的析构函数,对基类进行清理。

  • 多重继承:一个派生类同时继承多个基类
class D:public A,private B,protected C
{类D新增加的成员}
  • 多重继承派生类的构造函数
派生类构造函数名(总参数表列): 基类1构造函数(参数表列), 基类2构造函数(参数表列)... {
派生类中新增数成员据成员初始化语句
}
  • 各基类的排列顺序任意。

  • 派生类构造函数的执行顺序同样为: 先调用基类的构造函数,再执行派生类构造函数的函数体。

  • 调用基类构造函数的顺序按照声明派生类时基类出现的顺序

  • 多重继承引起的二义性问题:类C同时继承类A和类B。——>用域作用符来限定使用的是哪个

    • 两个基类有同名成员
    • 两个基类和派生类三者都有同名成员:派生类新增加的同名成员覆盖了基类中的同名成员。基类的同名成员在派生类中被屏蔽,成为“不可见”的。(函数名和参数个数相同、类型相匹配的情况下才发生同名覆盖)
    • 如果类A和类B是从同一个基类C派生的(钻石型)
      在这里插入图片描述在这里插入图片描述


  • 虚基类:使得在继承间接共同基类时只保留一份成员。
    如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员。在引用这些同名的成员时,必须在派生类对象名后增加直接基类名,以避免产生二义性。

  • 声明虚基类的一般形式为
    class 派生类名: virtual 继承方式 基类名

    • 经过这样的声明后,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次。
    • 为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类。否则仍然会出现对基类的多次继承。
      在这里插入图片描述
class A//声明基类A
 {};
class B :virtual public A           //声明类B是类A的公用派生类,A是B的虚基类
 {};
 虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。
 因为一个基类可以在生成一个派生类时作为虚基类,而在生成另一个派生类时不作为虚基类。
  • 虚基类的初始化
    • 如果在虚基类中定义了带参数的构造函数,而且没有定义默认构造函数,则在其所有派生类(包括直接派生或间接派生的派生类)中,通过构造函数的初始化表对虚基类进行初始化。
class A//定义基类A
 {
 A(int i){ }                         //基类构造函数,有一个参数};
class B :virtual public A           //A作为B的虚基类
 {
 B(int n):A(n){ }                   //B类构造函数,在初始化表中对虚基类初始化};
class C :virtual public A           //A作为C的虚基类
 {
 C(int n):A(n){ }                   //C类构造函数,在初始化表中对虚基类初始化};
class D :public B,public C     //类D的构造函数,在初始化表中对所有基类初始化
 {
 D(int n):A(n),B(n),C(n){ }};
  • 规定: 在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。
  • C++编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类(如类B和类C) 对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化。

有些面向对象的程序设计语言(如Java,Smalltalk)并不支持多重继承。容易引起二义性。

  • 基类与派生类的转换
    • 派生类对象可以向基类对象赋值。在赋值时舍弃派生类自己的成员。所谓赋值只是对数据成员赋值,对成员函数不存在赋值问题。不能用基类对象对其子类对象赋值
A a1;              //定义基类A对象a1
B b1;                //定义类A的公用派生类B的对象b1
a1 = b1;               //用派生类B对象b1对基类对象a1赋值
赋值后不能企图通过对象a1去访问派生类对象b1的成员,因为b1的成员与a1的成员是不同的。
  • 派生类对象可以替代基类对象 向基类对象的引用进行赋值或初始化。
A& r = a1;         //定义基类A对象的引用变量r,并用a1对其初始化
r = b1;   //用派生类B对象 b1 对 a1 的引用变量r赋值,此时r并不是b1的别名。它只是 b1 **中基类部分的别名**,r 与 b1 中基类部分共享同一段存储单元,r 与 b1 具有相同的起始地址。
A& r = b1;//定义基类A对象的引用变量r,并用派生类B对象b1对其初始化   
  • 如果函数的参数是基类对象或基类对象的引用,相应的实参可以用子类对象。

    void fun(A& r)//形参是类A的对象的引用变量
     {cout<<r.num<<endl;}            //输出该引用变量的数据成员num
    fun(b1);   //输出类B的对象b1的基类数据成员num的值。
    
  • 派生类对象的地址可以赋给指向基类对象的指针变量,也就是说,指向基类对象的指针变量也可以指向派生类对象。通过指向基类对象的指针,只能访问派生类中的基类成员,而不能访问派生类增加的成员。

  • 在这里插入图片描述
    在这里插入图片描述

  • 类的组合(composition):在一个类中以另一个类的对象作为数据成员的

类的组合和继承一样,是软件重用的重要方式。组合和继承都是有效地利用已有类的资源。但二者的概念和用法不同。继承是纵向的,组合是横向的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值