📔 第 15 章 友元、异常和其它 学习笔记
1. 友元¶
友元函数用于类的扩展接口 中,类中不仅可以使用友元函数,也可以将类作为友元。
友元类的所有方法都可以访问原始类的私有成员和保护成员。
在一个类中将另一个类声明为友元。
如果要将友元类中的特定的类成员解析作为原始类的友元,无需将整个类作为友元。
前向声明(forward declaration) 。
内联函数的链接性是内部的,意味着函数定义必须在使用函数的文件中。也可将定义放在实现文件中,但必须删除 关键字inline,此时的链接性是外部的。
将类成为彼此的友元的实现方式。例如:
2. 嵌套类¶
在C++中,可以将类声明放在另一个类中。在另一个类中声明的类被称为 嵌套类(nested class)。
当类声明位于公有部分时,才能包含类的外面使用嵌套类,而且必须使用作用域解析运算符。
对类进行嵌套和组合不同。
- 组合将类对象作为另一个类的成员。
- 对类进行嵌套不创建类成员,而是定义一种类型(类型仅包含嵌套类声明的类中有效)。
对类进行嵌套是为了实现另一个类,并避免名称冲突。
嵌套类的访问权限控制
- 嵌套类的
声明位置决定了嵌套类的作用域,决定了程序的哪些部分可以创建其类的对象。 - 和其它类一样,嵌套类的公有部分、私有部分和保护部分控制了对类成员的访问。
嵌套类访问的两种方式:
-
作用域

- 嵌套类的作用域为包含它的类,在类外部使用,则需要使用
类限定符。
- 嵌套类的作用域为包含它的类,在类外部使用,则需要使用
-
访问控制
-
对嵌套类访问控制规则与常规类相同。
类声明的位置决定了类的作用域可见性。 - 类可见后,访问控制规则(公有、保护、私有、友元)将决定程序对嵌套类成员的访问权限。
3. 异常¶
异常是C++相对较新的功能,早期老编译器中可能会没有实现,但新的编译器中则是默认关闭了该特性。所以需要使用编译器选项来开启。
3.1 返回abort()¶
调用位于头文件cstdlib(或stdlib.h)的Abort() 函数。
典型实现:想标准错误(即cerr使用的错误流)发送消息 abnormal program termination(程序异常终止) ,然后 终止程序 。还返回一个随实现而异的值,告知OS(如果程序是由另一个程序调用,则告诉父进程),处理失败。
abort()是否刷新文件缓冲区(用于存储读写到文件中的数据的内存区域)取决于实现。exit():会刷新文件缓冲区,则不显示消息。
一般情况下,显示程序的异常中断消息随编译器而不同。
3.2 程序错误码¶
一种比异常终止更灵活的方式:使用函数的返回值来指出问题。
任何数值都是有效的返回值,所以不存在可用于指出问题的特殊值。
一般使用指针参数或者引用参数来将值返回给调用程序,并使用函数的返回值来指出成功还是失败。
3.3 异常机制¶
C++异常是对程序运行过程中发生的异常情况的一种响应。
对异常的处理有3个组成部分:
-
引发异常
关键字throw表示引发异常,后面紧跟值用来指出异常特征。
-
使用
处理程序捕获异常关键字catch表示捕获异常,后面括号中紧跟类型声明来指出异常处理程序要响应的异常类型。其后的代码块则指出采取的措施。
-
使用
try块- 标识特定的异常
可能被激活的代码块,后面紧跟一个或多个catch块。 - 表面需要注意代码引起的异常。
- 标识特定的异常
3.4 异常规范和C++11¶
异常规范是C++98中的一项功能,在C++11中已摒弃。但也需要了解:
throw()部分是异常规范,可能出现在函数原型和函数定义中,可包含类型列表,也可不包含。
异常规范的两个作用:
- 告知可能需要使用
try块。 - 让编译器添加执行运行阶段
检查代码是否违反异常规范。
C++11中支持了一种特殊的异常规范:使用新增的 关键字noexcept 指出函数不会引发异常。
3.5 栈解退¶
当函数调用出现异常而终止,则程序释放栈中的内存,但不会释放栈的第一个返回地址后停止,而是继续释放栈,直到找到第一个 try块 中的返回地址,这个过程叫 栈解退。
异常机制将负责释放栈中的自动变量。
3.6 其它异常特性¶
引发异常时编译器总是创建一个临时拷贝,即使异常规范和catch块中指定的是引用
引用作为返回值的原因:避免创建副本以提高效率。
如果有一个异常类继承层次结构,排序catch块的规则:将捕获位于层次结构最下面的异常类的catch语句放在最前面,将捕获基类异常的catch语句放在最后面。
如果不知道异常的类型,方法是省略号来捕获任何异常。
如果可以预知一些异常类型,类似于switch语句的使用。
3.7 exception类¶
C++异常的主要目的:设计容错程序时避免一些错误处理方式。
在C++中 exception头文件中定义了 exception类,类中的 what() 虚拟成员函数,会返回一个字符串,字符串的特征随实现而异。
C++定义的基于exception的异常类型
-
stdexcept异常类头文件stdexcept定义的其它几个异常类,例如:logic_error和runtime_error类,都是从公有方式从exception派生而来。
-
这些类的构造函数接受一个string对象作为参数,参数提供了方法
what()以 C风格字符串方式返回字符数据。-
logic_error派生出来用于报告错误类型的类还有:- 逻辑错误,
任何阶段 domain_error:传递给函数的参数不在定义域内而引发异常invalid_error:传递了一个意料之外的值length_error:指出没有足够的空间来执行所需的操作。out_of_bounds:用于指示索引错误。
- 逻辑错误,
-
runtime_error 类型派生出来的类:
- 错误发生在
运行阶段 - range_error:不在函数允许的范围内,和上溢、下溢无关。
- overflow_error:上溢。
整型和浮点型都有可能。 - underflow_error:下溢。要发生在
浮点数计算。
- 错误发生在
-
-
每一个类都有自己的构造函数,使what()方法能够返回的字符串。
-
bad_alloc异常和new
- new 请求的内存
分配失败,则会引发bad_alloc的异常错误。 - 数组中最为常见。
- new 请求的内存
-
空指针和new
- new 分配内存失败,则会返回一个
空指针。也是从exception类派生而来。所以C++标准提供了用法:
- new 分配内存失败,则会返回一个
3.8 异常丢失¶
异常被引发后,会导致问题的两种情况:
-
意外异常(unexpected exception):在带异常规范的函数中引发,但必须与规范列表中的某种异常匹配。- 可以通过调用
terminate()(默认行为)、abort()或者exit()来终止程序 - 引发异常
- 可以通过调用
-
未捕获异常(uncaught exception):在没有try和catch块外抛出的异常。- 不会导致程序立即异常停止。
引发异常(第二种选择)的结果取决于unexpected_handler函数所引发的异常以及引发意外异常的函数的异常规范:
-
如果新引发的异常与原来的异常规范
匹配,则程序将从那里开始进行正常处理,即寻找与新引发的异常匹配的catch块。基本上是用预期的异常取代意外异常。 -
如果新引发的异常和原来的异常规范
不匹配,切异常规范中没包括std::bad_exception类型,则程序调用terminate()。bad_exception是从exception类派生而来。声明位于头文件execption中。 -
如果新引发的异常与原来
不匹配,且原来的异常规范中包含std::bad_exception类型,则不匹配的异常被std::bad_exception异常所取代。
如果要捕获所有的异常,则方法如下:
4. RTTI¶
RTTI(Runtime Type Identification,运行阶段类型识别),C++11中新添加的新特性。
RTTI旨在为程序在运行阶段确定对象的类型提供一种标准方式。
RTTI只适用于包含虚函数的类。
C++中支持RTTI的3个元素
dynamic_cast运算符:将使用一个指向基类的指针类生成一个指向派生类的指针,否则该运算返回0 ---- 空指针。typeid运算符:返回一个指出对象的类型的值type_info结构:存储有关特定类型的信息。
只有将RTTI用于包含虚函数的类层次结构,原因在于只有对于这种类层次结构,才应该将派生类对象的地址赋给基类指针。
4.1 dynamic_cast 运算符¶
是最常用的RTTI架构。
只有指针类型与对象的类型(或对象的直接或间接基类的类型)相同的类型转换才一定是安全的。
语法格式:
即使编译器支持RTTI,但在默认情况下,也可能是关闭该特性。
dynamic_cast 也可以用于引用。因为没有与空指针对应的引用值,所以无法使用特殊的引用值来指示失败。所以失败了,就会引发 bad_cast 的异常。
4.2 typeid 运算符 和 type_info 类¶
typeid 运算符使能够确定两个对象是否为同种类型。接受两种参数:
- 类名
- 结果为对象的表达式
typeid 运算符返回一个type_info对象的引用,其中 type_info 在头文件 typeinfo中定义的一个类。
typ_info类 重载 == 和 != 运算符,以便于使用对类型进行比较。
示例
typeid测试用来选择一种操作,因为操作不是类的方法,所以 不能通过类指针来调用它。
5. 类型转换运算符¶
假设 High 和 Low 是两个类。
通过4种类型转换运算符来使得转换过程增加规范。
-
dynamic_cast
- 能够在类层次结构中进行向上转换,而不允许其他转换。
-
const_cast
- 用于执行
只有一种用途的类型转换,即改变值为const或者volatile。语法和 dynamic_cast相同
- 用于执行
-
static_cast
- type_name 和 expression互相隐式转换为其所属的类型时,转换才合法,否则将出错。
-
reinterpret_cast
- 转换适用于依赖于实现的底层编程技术,具有不可移植性。
- 不做任何处理,也不能删除const属性
- 不支持所有的类型转换
- 可以将指针类型转换为足以存储指针表示的类型,但不能将指针转换为更小的整型或者浮点型。
- 不能将函数指针转换为数据指针。