• 第13章、类继承


    C++提供了比修改代码更好的方法来扩展和修改类。这种方法叫做类。

    派生一个类:

    派生类对象存储了基类的数据成员(派生类继承了基类的实现);
    派生类对象可以使用基类的方法(派生类继承了基类的接口);

    派生类在继承特性中添加什么呢?
    派生类需要自己的构造函数
    派生类可以根据需要添加额外的数据成员和成员函数。

    13.1.2 构造函数:访问权限的考虑

    派生类构造函数必须使用基类构造函数。
    创建派生类对象时,程序首先创建基类对象。从概念上说,这意味着基类对象应当在程序进入派生类构造函数之前被创建。C++使用成员初始化列表语法来完成这种工作。例如,下面是第一个RatedPlayer构造函数的代码:

    RatedPlayer::RatedPlayer(unsigned int r,const string &fn,const string &ln,bool ht):TableTennisPlayer(fn,ln,ht)
    {
    	rating =r;
    }
    
    • 1
    • 2
    • 3
    • 4

    其中TableTennisPlayer(fn,ln,ht)是成员初始化列表。它是可执行的代码,调用TableTennisPlayer构造函数。

    有关派生类构造函数的要点如下:
    1、首先创建基类对象;
    2、派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数;
    3、派生类构造函数应初始化派生类新增的数据成员。

    释放对象的顺序与创建对象的顺序相反,即首先执行派生类的析构函数,然后自动调用基类的析构函数。

    注意:创建派生类对象时,程序首先调用基类构造函数,然后再调用派生类构造函数。基类构造函数负责初始化继承的数据成员;派生类构造函数主要用于初始化新增的数据成员。派生类的构造函数总是调用同一个基类的构造函数。可以使用初始化器列表指明要使用的基类构造函数,否则将使用默认的基类构造函数。
    派生类对象过期时,程序将首先调用派生类析构函数,然后再调用基类析构函数。

    13.1.4 派生类和基类之间的特殊关系

    派生类与基类之间有一些特殊关系。其中之一是派生类对象可以使用基类的方法,条件是方法不是私有的。

    另外两个重要关系:基类指针可以在不进行显式类型转换的情况下指向派生类对象;基类引用可以在不进行显式类型转换的情况下引用派生类对象:

    RatedPlayer rplayer(1140,"Mallory","Duck",true);//派生类
    TableTennisPlayer *rt =rplayer;//TableTennisPlayer 基类
    TableTennisPlayer &pt =&rplayer;
    rt.Name(); //使用引用调用Name函数
    pt->Name();//使用指针调用Name函数
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然而,基类指针或引用只能用于调用基类的方法(Name 函数不是virtual函数),因此,这不能使用rt或pt来调用派生类的ResetRanking方法(此ResetRanking方法只在派生类中定义了)。

    通常,C++要求引用和指针类型与赋给的类型匹配,但这一规则对于继承来说是例外。然而,这种例外只是单向的,不可以将基类对象和地址赋给派生类和指针。

    如下,如果将RatedPlayer::Rating()方法用于TableTennisPlayer对象是没有意义的,因为TableTennisPlayer对象没有Rating()方法以及Rating()使用的一些成员。

    TableTennisPlayer  player("Besty","Bloop",true); // 基类
    RatedPlayer & rt =player;派生类 不允许
    RatedPlayer * rt =&player;派生类 不允许
    
    
    • 1
    • 2
    • 3
    • 4

    13.3 多态公有继承

    有两种重要的机制可用于实现多态公有继承:
    1、在派生类中重新定义基类的方法。
    2、使用虚方法(virtual)。

    在类对象使用成员方法时:如果方法是通过引用或指针而不是对象调用的,它将确定使用哪一种方法。如果没有使用关键字virtual,程序将根据引用类型或指针类型来选择方法。如果使用了关键字virtual, 程序将根据引用或指针指向的对象的类型来选择方法。

    如果View()不是虚的,则程序的行为如下:

    Brass dom;
    BrassPlus dot;
    
    Brass & b1=dom;
    Brass & b2=dot;
    b1.View();// 使用Brass::View()
    b2.View();// 使用Brass::View()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    如果View()是虚的,则程序的行为如下:

    Brass dom;
    BrassPlus dot;
    
    Brass & b1=dom;
    Brass & b2=dot;
    b1.View();// 使用Brass::View()
    b2.View();// 使用BrassPlus ::View()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    基类声明了一个虚析构函数。这样做是为了确保释放派生对象时,按照正确的顺序调用析构函数。

    4、为何需要虚析构函数

    如果析构函数不是虚的,则将只调用对应于指针类型的析构函数。

    如果析构函数是虚的,将调用相应对象类型的析构函数。因此,如果指针指向的是BrassPlus对象,将调用BrassPlus的析构函数就,然后自动调用基类的析构函数。因此,使用析构函数可以确保正确的析构函数序列被调用。

    13.4 静态联编和动态联编

    将源代码中的函数调用解释为执行特定函数代码块被称为函数名联编。在编译过程中进行联编被称为静态联编,又称为早期联编。编译器能够在程序运行时选择正确的虚方法的代码,这被称为动态联编,又称为晚期联编。

    将派生类引用或指针转换为基类引用或指针被称为向上强制转换,这使公有继承不需要进行显式类型转换。

    将基类指针或引用转换为派生类指针或引用被称为向下强制转换。如果不使用显式类型转换,则向下强制类型转换是不允许的。

    编译器对非虚方法使用静态联编。对虚方法使用动态联编。

    2、虚函数的工作原理

    通常,编译器处理虚函数的方法是:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址的指针。这种数组被称为虚函数表。虚函数表中储存了为类对象进行声明的虚函数的地址。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    调用虚函数时,程序将查看存储在对象中的vtbl地址,然后转向相应的函数地址表。

    总之,使用虚函数时,在内存和执行速度方面有一定的成本,包括:
    1、每个对象都将增大,增大量为存储地址的空间;
    2、对于每个类,编译器都创建一个虚函数地址表(数组)
    3、对于每个函数调用,都需要执行一项额外的操作,即到表中查找地址。
    虽然非虚函数的效率比虚函数稍微,但是并不具备动态联编功能。

    13.4.3 有关虚函数注意事项

    虚函数的一些要点。
    1、在基类方法的声明中使用关键字virtual可以使用该方法在基类以及所有的派生类(包括从派生类派生出来的类)中是虚的。
    2、如果使用指向对象的引用或者指针来调用虚方法,程序将使用为对象类型定义的方法,而不是使用为引用或指针类型定义的方法。这种被称为动态联编或晚期联编。这种行为非常重要,因为这样基类指针或引用可以指向派生类对象。
    3、如果定义的类将被用作基类,则应将那些要在派生类中重新定义的类方法声明为虚函数。

    1、构造函数
    构造函数不能是虚函数。派生类不继承基类的构造函数,所以将类构造函数声明为虚函数的没有意义

    2、析构函数
    析构函数应当是虚函数,除非类不用做基类/
    提示:通常应给基类提供一个虚析构函数,即使它并不需要析构函数。

    3、友元
    友元不能是析构函数,因为友元不是类成员。而只有成员才能是虚函数。

    4、没有重新定义
    如果派生类没有重新定义函数,将使用该函数的基类版本。

    5、重新定义将隐藏

    在这里插入图片描述

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

    13.7 继承和动态内存分配

    13.7.1 情况一:派生类不使用new

    派生类不需要定义显式析构函数、复制构造函数和赋值运算符。

    13.7.2 情况二:派生类使用new

    在这种情况下,必须为派生类定义显式析构函数、复制构造函数和赋值运算符。

    13.8.3 公有继承的考虑因素

    1、is-a关系
    在某些情况下,最好的方法可能是创建包含纯虚函数的抽象数据类,并从它派生出其他的类。

    2、什么不能被继承

    构造函数不能继承的。创建派生类对象时,必须调用派生类的构造函数。然而,派生类构造函数通常使用成员初始化列表语法来调用基类构造函数,以创建派生对象的基类部分。

    3、友元函数
    由于友元函数并非类成员,因此不能继承。

    在这里插入图片描述

    3、有关使用基类方法的说明

    以公有方式派生的类的对象可以通过多种方式来使用基类的方法
    1、派生类对象自动使用继承而来的基类方法,如果派生类没有重新定义该方法。
    2、派生类的构造函数自动调用基类的构造函数。
    3、派生类的构造函数自动调用基类的默认构造函数,如果没有在成员初始化列表中指定其他构造函数。
    4、派生类构造函数显式地调用成员初始化列表中指定的基类构造函数。
    5、派生类方法可以使用作用域解析运算符来调用公有的和受保护的基类方法。
    6、派生类的友元函数可以通过强制类型转换,将派生类引用或指针转换为基类引用或指针,然后使用该引用或指针来调用基类的友元函数。

    在这里插入图片描述

    来源:C++ Primer Plus
    仅供学习,侵删

  • 相关阅读:
    Mesh形变算法
    dva搭建项目实例
    Redis 管道详解
    Dubbo之启动时检查(check属性)。
    学生身份标签的识别与风控应用
    膀胱结石的危害是怎么样的?
    还不知道图片去水印怎么去?看看这篇文章就知道了
    windows下查看端口及占用端口的进程
    [刷题]队列
    什么是HTML?
  • 原文地址:https://blog.csdn.net/qq_30457077/article/details/124788076