C++程序设计案例教程(学习笔记)——Cha6继承

  • 6.1 继承的概念

6.1.1 继承的概念

继承是由一个已有类创建一个新类的过程。已有类称为基类或父类,新类称为派生类或子类。派生类从基类继承其成员,并根据需要添加新的成员,或对原有的成员进行改造(改写),以适应新类的需求。

6.1.2 派生类的定义格式

定义格式如下:
class <派生类名> :<继承方式> <基类名>
{
<派生类新增成员说明>
}
派生类名:通过继承派生的新类的名字。
基类名: 派生类的基类,必须是以声明过的类。
继承方式:有关键字public(公有继承);private(私有继承);protected(保护继承。)如果省略,则默认是private。

6.1.2 派生类对象的结构
   1.继承基类成员:(除构造和析构函数外)继承基类全部成员,作为派生类成员的一部分。
   2.添加新成员:派生类在基类的基础上,根据需要添加新成员,包括数据成员和成员函数。
   3.改造基类原有的成员:通过定义一个与基类成员同名的成员来实现。既可以是数据成员也可以是成员函数,新成员对基类的成员进行“覆盖”,当通过派生类对象调用同名成员是,系统自动调用派生类中的新定义的成员,如果是成员函数必须保证派生类和基类的同名函数的参数必须相同,否则被认为是重载。
   在派生类中添加的新成员和改写的成员统称为派生类成员。
   派生类对象包括两个一部分是派生类成员不能,一部分是基类成员。

  • 6.2继承方式

   采用不同的继承方式,从基类继承而来的成员在派生类中的访问权限也将有所不同。

6.2.1 公有继承
   公有继承如下特点:
    (1)基类的公有成员在派生类中仍然为公有成员,可有派生类成员或派生类对象直接访问。
    (2)基类的私有成员在派生类中不可见,只能通过基类的非私有成员函数访问。
    (3)基类的保护有成员在派生类中仍然为保护成员,可有派生类的成员直接访问,但不能通过派生类对象直接访问。
6.2.2 私有继承
   私有继承如下特点:
    (1)基类的公有成员和保护成员被继承后均成为派生类的私有成员,可由派生类的成员函数直接访问,但不能通过派生类对象直接访问。
    (2)基类的私有成员在派生类中不可见,只能通过基类非私有成员函数访问。
注:私有继承后,基类的公有成员和保护成员都是派生类的私有成员,类外无法通过类对象来直接访问,因此在派生类中增加新的公有成员函数,类对象可通过这些公有成员函数访问从基类继承来的成员。由于再进过一次派生,基类的成员不可见,无法直接访问,因此私有继承尽量少使用。
6.2.3 保护继承
   保护继承如下特点:
    (1)基类的公有成员和保护成员被继承后均成为派生类的保护成员,可由派生类的成员函数直接访问,但不能通过派生类对象直接访问。
    (2)基类的私有成员在派生类中不可见,只能通过基类非私有成员函数访问。
总上述:对派生类从基类继承成成员的访问方式分一下四类。
       1.公有的:派生类成员或派生类对象直接访问。
       2.不可见的:基类的私有成员在派生类中不可见只能通过基类非私有成员函数访问。
       3.保护的:可由派生类的成员函数直接访问,但不能通过派生类对象直接访问。
       4.私有的:可由派生类的成员函数直接访问,但不能通过派生类对象直接访问。

在这里插入图片描述

  • 6.3 派生类的构造函数和析构函数
        派生类对象的成员一部分来自基类,一部分是派生类新添加的。在创建派生类对象时,系统首先调用基类的构造函数来初始化来自基类的数据成员;再调用派生类的构造函数初始化派生类新添加的数据成员,析构函数调用的顺序相反。
        如果基类或派生类没有显示地定义构造函数,系统会自动调用默认的构造函数。
        如果基类的构造函数有参数,派生类的构造函数也有参数,并且派生类的成员可能是其他类的对象,这时通过派生类构造函数的参数表,初始化列表方式来传递参数。
    格式如下:
    <派生类名>(<参数总表>):<基类构造函数名>(<参数表1>) 《,<子对象名>(<参数表2>)》
    注:《》可省略。一般不再构造函数输出数据
    执行时的调用顺序:
    (1)调用基类构造函数。
    (2)调用子对象所属的构造函数(如果有)。
    (3)调用派生类的构造函数。

  • 6.4多继承
    6.4.1 多继承的概念
        派生类具有两个或两个以上的基类,这样的继承结构称为多重继承。派生类继承了所以基类的成员并可以添加新成员,或对基类成员进行改造。
        多继承声明格式如下:
    class<派生类名>:<继承方式1><基类名1>,<继承方式2> <基类名2>,…,<继承方式n><基类名>
    {
    <派生类新增成员声明>
    } ;
    注:如果省略基类名前的继承方式则默认是私有继承。
    6.4.2 多继承下的构造函数
    派生类构造函数格式如下:
    <派生类名>(<参数总表>):<基类名1>(参数表1) 《,<基类名2>(<参数表2>),…,<基类名n>(<参数表n>)》《,<子对象1>(<子对象参数表1>),…,<子对象m>(<子对象参数表m>)》
    {
    <派生类新增成员的初始化>
    };
    说明:《》里内容可省略。
    冒号后的<基类名>和<子对象名>的顺序可以是任意的,冒号后所以参数都在<参数总表中>出现。执行的调用顺序:
        (1)调用基类的构造函数,调用顺序取决于定义派生类时派生列表中基类的声明顺序。
        (2)调用子对象所属类的构造函数,调用顺序取决于对象成员在派生类中的声明顺序。
        (3)调用派生类的构造函数。
    注:如果这里的基类指的是派生类直接基类。如果基类没有定义构造函数,或提供了无参的构造函数,则派生类构造函数的初始化列表中可以不出现基类,否则就要显示地给出基类名和参数表。析构函数的调用顺序与构造函数顺序相反。
    6.4.3 二义性
        多继承中,不允许直接对同一基类继承两次或两次以上,以防止在派生类中出现基类的多个副本。当派生类中出现了来自不同基类的同名成员时;或者多个基类具有公共的基类时,公共基类的成员会在派生类中被多次继承,这些会造成标识符的不唯一,称为二义性。
          1.同名成员被继承产生二义性,这时通过使用作用域运算符“::”来解决。或者通过在派生类中增加与基类成员同名成员方式进行同名覆盖。当派生类对象调用同名成员时,直接自动调用派生类中新增的成员函数。
          2.同一基类被多次继承产生二义性,这时通过使用作用域运算符“::”来解决。但必须派生类的直接基类名,而不是公共基类名。

  • 6.5虚基类
    DUO
    一般只需要基类A的一个副本,这时可以声明虚基来实现。在公共基类A派生类B和C时,可以在派生列表中将公共基类声明为虚基类,这样在类D的对象中只有公共基类A的一个副本,解决了公共基类所带来的二义性。
    6.5.1 虚基类的定义
    虚基类的声明形式如下:
    virtual <继承方式> <基类名>
    其中virtual是声明虚基类的关键字,也可以放在<继承方式>后面,后面的基类为虚基类。
    如图中的叙述:
    class B : virtual public A{ };
    class C : virtual public A{ };
    class D : public B,public C{ };
    //将A类声明为虚基类,派生类只有A的一个副本
    继承方式如下图:
    在这里插入图片描述
    6.5.2 虚基类的构造函数和析构函数
       如上图所述,创建派生类D的对象时,会调用直接基类的B和C的构造函数,如果B和C右都调用了基类A的构造函数就会多出初始化类A的数据成员。
      所以虚基类的构造函数只能别调用一次。在多次继承的情况中,将建立对象的所指定的类称为最终派生类,虚基类的构造函数是由最终派生类的构造函数调用,在最终派生类的初始化列表中必须出现对基类构造函数的调用,如果不列出,则表示虚基类的有默认构造函数,否则将会出现错误。
       虚基类构造函数的调用顺序
    (1)调用虚基类的构造函数。
    (2)调用直接基类的构造函数,调用顺序与继承声明顺序相同。
    (3)调用最终派生类的构造函数。
    释放对象的析构函数于其相反。

  • 6.6基类转换
    6.6.1 派生类到基类的转换
      1.用派生类到基类的转换
      用派生类对象对基类对象进行初始化或赋值,将派生类对象基类部分的成员赋值给基类对象对应成员。即在初始化时调用构造函数,赋值时调用赋值运算符完成的。
    例:class Base {…};
           class Derived:public Base{…};
           Derived d_obj;
           Base b_obj1;
           b_obj1=d_obj; //用派生类对象对基类进行赋值
           Base b_obj2(d_obj);//用派生类对象对基类对象进行初始化
      2.用派生类的引用或对象初始化基类的引用
      可以将基类的引用绑定到派生类的对象上,也可以直接用派生类的引用对基类引用进行初始化。 基类引用绑定到派生类对象上后,通过基类引用只能访问派生类中的基类成员。
    例:class Base {…};
           class Derived:public Base{…};
           Derived d_obj, &d_r=d_obj;
           Base &b_r1=d_obj; //将基类引用b_r1绑定到派生类对象d_obj上
           Base &b_r2=d_r;//用派生类引用d_r给基类引用b_r2赋值
      3.用派生类对象的地址或派生类指针初始化基类指针
      用基类指针指向派生类对象,或者将派生类指针赋值给基类指针,基类指针指向派生类对象后,只能访问派生类对象中继承自基类的成员。
    例:class Base {…};
           class Derived:public Base{…};
           Derived d_obj, *d_p=&d_obj;
           Base *b_p1=&d_obj;/将派生类对象的地址赋给基类指针
           Base *b_p2=d_p;//将派生类指针赋给基类指针
    总上:无论使用引用和指针,如果要访问派生类中新增的成员,必须对它们进行强制类型转换。
    6.6.1 基类到派生类的转换
       基类对象只包括基类成员,不包括派生类的成员,所以从基类到派生类不存在自动转换。
    例:class Base {…};
           class Derived:public Base{…};
           Base base;
           Derived derived=base;//错,不能用基类对象给派生类对象赋值
           Derived *derived _p=&base;//错,不能用基类对象的地址给派生类指针赋值
           Derived &derived_r=base;//错,不能将派生类引用绑定到基类对象上
       需要派生类对象的地方不能使用基类对象 ,也不能用基类对象给派生类对象赋值,不能将派生类引用绑定到基类对象上。
    即使基类指针指向的是派生类对象,也不能将它的值赋给派生类指针,引用亦是如此。
    例:class Base {…};
           class Derived:public Base {…};
           Derived derived;
           Base *base_p=&derived;//对,可以将派生类对象地址赋给基类指针。
            Derived *derived_p=&base_p;//错,不能将基类地址赋给派生类指针变量。

猜你喜欢

转载自blog.csdn.net/qq_40719550/article/details/83275912