第13章 类继承

1. 如果购买厂商的C库,除非厂商提供库函数的源代码,否则您将无法根据自己的需求,对函数进行扩展或修改。但如果是类库,只要其提供了类方法的头文件和编译后的代码,仍可以使用库中的类派生出新的类。而且可以在不公开实现的情况下将自己的类分发给其他人,同时允许他们在类中添加新特性。

2. 派生类构造函数首先创建基类对象,如果不调用基类构造函数,程序将使用默认的基类构造函数。

3. 创建派生类对象时,程序首先调用基类构造函数,然后再调用派生类构造函数。派生类对象过期时,程序将首先调用派生类析构函数,然后再调用基类析构函数。

4. 派生类对象可以使用基类的方法,条件是方法不是私有的。

5. 基类指针可以在不进行显式类型转换的情况下指向派生类对象;基类引用可以在不进行显式类型转换的情况下引用派生类对象。然而,基类指针或引用只能用于调用基类方法,不能调用派生类方法。

这种兼容性使得可以用派生类对象来初始化基类对象,也可以将派生类对象赋给基类对象。

6. 通常,C++要求引用和指针类型与赋给的类型匹配,但这一规则对继承来说是个例外。

7. 如果要在派生类中重新定义基类的方法,通常应将基类方法声明为虚的virtual,程序将根据引用或指针指向的对象的类型来选择方法。如果没有使用关键字virtual,程序将根据引用类型或指针类型来选择方法。

8. 方法在基类中声明为虚的后,它在派生类中将自动成为虚方法。然而,在派生类声明中使用关键字virtual来指出哪些函数是虚函数也不失为一个好办法。

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

10. 关键字virtual只用于类声明中,而没有用于方法定义中。

11. 为何需要虚析构函数?

如果析构函数不是虚的,则将只调用对应于指针类型的析构函数,即使指针指向派生类对象。如果析构函数是虚的,如果基类指针指向派生类对象,将调用派生类对象的析构函数,然后自动调用基类的析构函数。因此,使用虚析构函数可以保证正确的析构函数序列被调用。

12. 将源代码中的函数调用解释为执行特定的函数代码块称为函数名联编(binding)。在C语言中,这非常简单,因为每个函数名都对应一个不同的函数。在C++中,由于函数重载的缘故,这些任务更复杂。在编译过程进行联编被称为静态联编(static binding),又称为早期联编(early binding)。编译器必须生成能够在程序运行时选择正确的虚方法的代码,这称为动态联编(dynamic binding),又称为晚期联编(late binding)。

在C++中,动态联编与通过指针和引用调用方法相关,从某种程度上说,这是由继承控制的。

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

14. 动态联编让您能够重新定义类方法,为什么不将它设置为默认的?原因有两个——效率和概念模型。

为使程序能够在运行阶段决策,必须采取一些方法来跟踪基类指针或引用指向的对象类型,这增加了额外的处理开销。由于静态联编的效率更高,因此被设置为C++的默认选择。

在设计类时,可能包含一些不在派生类重新定义的成员函数。不将这些函数设置为虚函数,有两方面的好处。首先,效率更高;其次,指出不要重新定义该函数。这表明,如果要在派生类中重新定义基类的方法,则将它们设置为虚方法;否则,设置为非虚方法。

15. 虚函数的工作原理P504

编译器处理虚函数的方法是:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为虚函数表。编译器为每个类创建1个虚函数表,为该类的所有对象共享。无论类中包含的虚函数是1个还是10个,都只需要在对象中添加1个地址成员,只是表的大小不同而已。

调用虚函数时,程序将查看存储在对象中的虚函数表的地址,然后转向相应的函数地址表。如果使用类声明中定义的第一个虚函数,则程序将使用数组中的第一个函数地址,并执行具有该地址的函数。如果使用声明中的第三个虚函数,程序将使用地址为数组中第三个元素的函数。

总之,使用虚函数,在内存和执行速度方面有一定的成本,包括:

1)  每个对象都将增大,增大量为存储虚函数表的地址的空间;

2)  对于每个类,编译器都创建一个虚函数地址表(数组);

3)  对于每个函数调用,都需要执行一项额外的操作,即到表中查找地址。

16. 构造函数不能是虚函数。

基类应提供一个虚析构函数,即使它不需要析构函数。

17. 如果派生类没有重新定义虚函数,则将使用该函数的基类版本。

18. 重新定义继承的方法并不是重载,将隐藏方法。P506

19. 如果基类中函数被重载了,则应在派生类中重新定义所有的基类版本。否则没有重定义的版本将被隐藏,派生类对象无法使用它们。

  1. 20.  private和protected的区别只有在基类派生的类中才会表现出来。派生类的成员可以直接访问基类的保护成员,但不能直接访问基类的私有成员。因此,对于外部世界来说,保护成员的行为和私有成员相似;但对于派生类来说,保护成员的行为与公有成员相似。

21. 最好对类数据成员采用私有访问控制,不要使用保护访问控制;同时通过基类方法使派生类能够访问基类数据。

然而,对于成员函数来说,包含访问控制很有用,它让派生类能够访问公众不能使用的内部函数。

22. 至少包含一个纯虚函数的类称为抽象基类。

C++通过使用纯虚函数提供未实现的函数,纯虚函数声明的结尾处为=0,参见Area()方法。

当类声明中包含纯虚函数时,则不能创建该类的对象。

23. 总之,在原型中使用=0指出类是一个抽象基类,在类中可以不定义该函数,也可以定义。

24. 可以将ABC看作是一种必须实施的接口。ABC要求具体派生类覆盖其纯虚函数——迫使派生类遵循ABC设置的借口规则。

25. 如果基类使用动态内存分配,并重新定义析构函数、赋值函数、复制构造函数,而派生类不使用动态内存分配,则派生类不需要定义显示析构函数、复制构造函数和赋值运算符。

首先,来看是否需要析构函数。如果没有定义析构函数,编译器将定义一个不执行任何操作的默认析构函数。该派生类析构函数执行自身代码后,调用基类析构函数。

接着看复制构造函数。默认复制构造函数执行成员复制,但复制类成员或继承的类组件时,则是使用该类的复制构造函数完成的。

对于赋值来说,也是如此。类的默认赋值运算符将自动使用基类的赋值运算符来对基类组件进行赋值。因此,默认赋值运算符也是合适的。

26. 当基类和派生类都采用动态内存分配时,派生类的析构函数、复制构造函数、赋值运算符函数都必须显式提供,而且都必须使用相应的基类方法来处理基类元素。这种要求是通过三种不同的方式来满足的。对于析构函数,这是自动完成的;对于构造函数,这是通过在初始化列表中调用基类的复制构造函数来完成的;如果不这样做,将自动调用基类的默认构造函数。对于赋值运算符,这是通过使用作用域解析运算符显式地调用基类的赋值运算符来完成的。P518

27. 对于基类,即使它不需要析构函数,也应提供一个虚析构函数。

28. 构造函数、析构函数、赋值运算符是不能被继承的。

29. 可以将派生类对象赋给基类对象,但此时赋值运算符只负责基类成员。

30. 问题“是否可以将基类对象赋给派生类对象”的答案是“也许”。如果派生类对象包含了这样的构造函数,即对将基类对象转换为派生类对象进行了定义(转换构造函数),则可以将基类对象赋给派生类对象。如果派生类定义了用于将基类赋给派生类对象的赋值运算符,则也可以这样做。如果上述条件都不满足,则不能这样做,除非使用显式强制类型转换。

31. 由于友元函数并非类成员,因此不能继承。然而,您可能希望派生类的友元函数能够使用基类的友元函数。为此,可以通过强制类型转换符,将派生类引用或指针转换为基类引用或指针,然后使用转换后的指针或引用来调用基类的友元函数。

32. 派生类方法可以使用作用域解析运算符类调用公有的和受保护的基类方法。

C++ primer plus读书笔记——第13章 类继承的更多相关文章

  1. C++ primer plus读书笔记——第12章 类和动态内存分配

    第12章 类和动态内存分配 1. 静态数据成员在类声明中声明,在包含类方法的文件中初始化.初始化时使用作用域运算符来指出静态成员所属的类.但如果静态成员是整形或枚举型const,则可以在类声明中初始化 ...

  2. APUE读书笔记-第13章-守护进程

    第13章 守护进程 13.1 引言 *守护进程也称精灵进程(daemon)是生存期较长的一种进程.它们常常在系统自举时启动,仅在系统关闭时才终止.因为它们没有控制终端,所以说它们是在后台运行的.UNI ...

  3. C++ primer plus读书笔记——第15章 友元、异常和其他

    第15章 友元.异常和其他 1. 友元类的所有方法都可以访问原有类的私有成员和保护成员.另外,也可以做更严格的限制,只将特定的成员函数指定为另一个类的友元.哪些函数.成员函数.或类为友元是由类定义的, ...

  4. C++ primer plus读书笔记——第14章 C++中的代码重用

    第14章 C++中的代码重用 1. 使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现).获得接口是is-a关系的组成部分.而使用组合,类可以获得实现,但不能获得接口. ...

  5. C++ primer plus读书笔记——第11章 使用类

    第11章 使用类 1. 运算符重载是一种形式的C++多态. 2. 不要返回指向局部变量或临时对象的引用.函数执行完毕后,局部变量和临时对象将消失,引用将指向不存在的数据. 3. 运算符重载的格式如下: ...

  6. Javascript设计模式与开发实践读书笔记(1-3章)

    第一章 面向对象的Javascript 1.1 多态在面向对象设计中的应用   多态最根本好处在于,你不必询问对象“你是什么类型”而后根据得到的答案调用对象的某个行为--你只管调用行为就好,剩下的一切 ...

  7. C++ primer plus读书笔记——第17章 输入、输出和文件

    第17章 输入.输出和文件 1. 对键盘进行输入缓冲可以让用户在将输入传输给程序之前返回并更正.C++程序通常在用户按下回车键时刷新输入缓冲区. 2. 一些I/O类 streambuf类为缓冲区提供了 ...

  8. C++ primer plus读书笔记——第16章 string类和标准模板库

    第16章 string类和标准模板库 1. string容易被忽略的构造函数: string(size_type n, char c)长度为n,每个字母都为c string(const string ...

  9. C++ primer plus读书笔记——第9章 内存模型和名称空间

    第9章 内存模型和名称空间 1. 头文件常包含的内容: 函数原型. 使用#define或const定义的符号常量. 结构声明. 类声明. 模板声明. 内联函数. 2. 如果文件名被包含在尖括号中,则C ...

随机推荐

  1. P1579_哥德巴赫猜想(JAVA语言)

    题目背景 1742年6月7日哥德巴赫写信给当时的大数学家欧拉,正式提出了以下的猜想:任何一个大于9的奇数都可以表示成3个质数之和.质数是指除了1和本身之外没有其他约数的数,如2和11都是质数,而6不是 ...

  2. 攻防世界 reverse 666

    666  2019_UNCTF main int __cdecl main(int argc, const char **argv, const char **envp) { char myen; / ...

  3. PTA 求链表的倒数第m个元素

    6-7 求链表的倒数第m个元素 (20 分)   请设计时间和空间上都尽可能高效的算法,在不改变链表的前提下,求链式存储的线性表的倒数第m(>)个元素. 函数接口定义: ElementType ...

  4. 对象存储服务-Minio

    Mino 目录 Mino 对象存储服务 Minio 参考 Minio 架构 为什么要用 Minio 存储机制 纠删码 MinIO概念 部署 单机部署: Docker 部署Minio 分布式Minio ...

  5. effective解读-第八条 避免使用finalizer和Cleaner

    java9之前finalizer,java9使用cleaner代替了finalizer.相比finalizer,cleaner(它存在于一个独立类Cleaner中,需要时候注入到对应类中即可)不会污染 ...

  6. PAT (Basic Level) Practice (中文) 1050 螺旋矩阵 (25 分) 凌宸1642

    PAT (Basic Level) Practice (中文) 1050 螺旋矩阵 (25 分) 目录 PAT (Basic Level) Practice (中文) 1050 螺旋矩阵 (25 分) ...

  7. Java利用线程工厂监控线程池

    目录 ThreadFactory 监控线程池 扩展线程池 扩展线程池示例 优化线程池大小 线程池死锁 线程池异常信息捕获 ThreadFactory 线程池中的线程从哪里来呢?就是ThreadFoct ...

  8. [Fundamental of Power Electronics]-PART II-9. 控制器设计-9.2 负反馈对网络传递函数的影响

    9.2 负反馈对网络传递函数的影响 我们已经知道了如何推导开关变换器的交流小信号传递函数.例如,buck变换器的等效电路模型可以表示为图9.3所示.这个等效电路包含三个独立输入:控制输入变量\(\ha ...

  9. [Fundamental of Power Electronics]-PART I-5.不连续导电模式-5.2 变比M分析

    5.2 变比M分析 经过一些改进,第二章中的用于CCM稳态分析的相同技术和近似方法可以应用于DCM. (a)电感伏秒平衡.电感电压直流分量必须为0: \[<v_{L}>=\frac{1}{ ...

  10. C#修改AD账号及密码

    在使用AD域环境搭建的账号系统修改密码的时候比较麻烦一般需要管理员在域环境去进行对用户的密码进行修改. 以下就是用来查询和修改AD域密码的方法. 1 /// <summary> 2 /// ...