C++ primer plus读书笔记——第13章 类继承
第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. 如果基类中函数被重载了,则应在派生类中重新定义所有的基类版本。否则没有重定义的版本将被隐藏,派生类对象无法使用它们。
- 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章 类继承的更多相关文章
- C++ primer plus读书笔记——第12章 类和动态内存分配
第12章 类和动态内存分配 1. 静态数据成员在类声明中声明,在包含类方法的文件中初始化.初始化时使用作用域运算符来指出静态成员所属的类.但如果静态成员是整形或枚举型const,则可以在类声明中初始化 ...
- APUE读书笔记-第13章-守护进程
第13章 守护进程 13.1 引言 *守护进程也称精灵进程(daemon)是生存期较长的一种进程.它们常常在系统自举时启动,仅在系统关闭时才终止.因为它们没有控制终端,所以说它们是在后台运行的.UNI ...
- C++ primer plus读书笔记——第15章 友元、异常和其他
第15章 友元.异常和其他 1. 友元类的所有方法都可以访问原有类的私有成员和保护成员.另外,也可以做更严格的限制,只将特定的成员函数指定为另一个类的友元.哪些函数.成员函数.或类为友元是由类定义的, ...
- C++ primer plus读书笔记——第14章 C++中的代码重用
第14章 C++中的代码重用 1. 使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现).获得接口是is-a关系的组成部分.而使用组合,类可以获得实现,但不能获得接口. ...
- C++ primer plus读书笔记——第11章 使用类
第11章 使用类 1. 运算符重载是一种形式的C++多态. 2. 不要返回指向局部变量或临时对象的引用.函数执行完毕后,局部变量和临时对象将消失,引用将指向不存在的数据. 3. 运算符重载的格式如下: ...
- Javascript设计模式与开发实践读书笔记(1-3章)
第一章 面向对象的Javascript 1.1 多态在面向对象设计中的应用 多态最根本好处在于,你不必询问对象“你是什么类型”而后根据得到的答案调用对象的某个行为--你只管调用行为就好,剩下的一切 ...
- C++ primer plus读书笔记——第17章 输入、输出和文件
第17章 输入.输出和文件 1. 对键盘进行输入缓冲可以让用户在将输入传输给程序之前返回并更正.C++程序通常在用户按下回车键时刷新输入缓冲区. 2. 一些I/O类 streambuf类为缓冲区提供了 ...
- C++ primer plus读书笔记——第16章 string类和标准模板库
第16章 string类和标准模板库 1. string容易被忽略的构造函数: string(size_type n, char c)长度为n,每个字母都为c string(const string ...
- C++ primer plus读书笔记——第9章 内存模型和名称空间
第9章 内存模型和名称空间 1. 头文件常包含的内容: 函数原型. 使用#define或const定义的符号常量. 结构声明. 类声明. 模板声明. 内联函数. 2. 如果文件名被包含在尖括号中,则C ...
随机推荐
- TypeError: 'list' object cannot be interpreted as an integer Python常见错误
想要通过索引来迭代一个list或者string的元素, 这需要调用 range() 函数.要记得返回len 值而不是返回这个列表.
- 关于Green AI
上一篇文章提到了模型不环保这个话题.这篇文章就这个问题展开唠叨一下. 自从BERT, GPT此类的大型模型诞生以来,小作坊们除了把pre-trained的模型拿过来微调一下,就束手无策了,因为成本实在 ...
- 创建逻辑卷,格式化为xfs格式化,在线扩容
创建逻辑卷,并且格式化为xfs格式化好,然后在线扩容 删除逻辑卷组
- 11. man page,info page
Linux系统中的命令可分为内部命令和外部命令.内部命令,又称为内建命令(builtin).怎么区分内部命令和外部命令了? 输入man bash命令,就可查看所有的内部命令. 如何查看命令使用方法 内 ...
- 一次死锁导致CPU异常飘高的整个故障排查过程
目录 一.问题详情 top 命令截图 联系腾讯云排查 检查系统日志发现异常 二. 问题解析 三.问题原因 最终结论 四.扩展 进程的几种状态 马后炮 如何快速清理僵尸进程(Z) 内核参数相关 如何查看 ...
- 数据结构之Queue | 让我们一块来学习数据结构
前面的两篇文章分别介绍了List和Stack,下面让我们一起来学习Queue 数据结构之List | 让我们一块来学习数据结构 数据结构之Stack | 让我们一块来学习数据结构 队列的概况 队列是一 ...
- kubernetes的Deployment, DaemonSet, Job 和 CronJob事例
k8s kubernetes给node节点添加标签和删除node节点标签 Deployment配置文件exampledeploymentv1.yaml apiVersion: apps/v1 kind ...
- K8S(18)容器环境下资源限制与jvm内存回收
K8S(18)容器环境下资源限制与jvm内存回收 目录 K8S(18)容器环境下资源限制与jvm内存回收 一.k8s中的java资源限制与可能的问题 方案1:通过JVM的Xms和Xmx参数限制 方案2 ...
- 三个dom xss常用tips
分享dom xss的三个案例 (1)javascript里面过滤单引号和双引号? 搭建环境: 只是过滤了单引号和双引号是可以xss的: 使用<>闭合script即可 </script ...
- 『动善时』JMeter基础 — 3、JMeter插件管理
JMeter是一个Java开发的开源软件,开源的软件有一个好处,就是会有很多第三方开发出来的插件,使得JMeter在处理某一些功能的时候更加的方便.并且这些插件拿过来就可以使用,完全免费的. 我们安装 ...