《C++ Primer Plus》 第十五章
第十五章:友元、异常和其他
15.1 友元
类并非只能拥有友元, 也可以将类作为友元。
友元类的所有方法都可以访问原始类的私有成员和保护成员。
也可以更严格的限制, 只将特定的成员函数作为另一个类的友元。
那些函数、成员函数或类为友元是由类定义的, 而不能从外部强加友情。
友元类
例:遥控器(Remote)和电视机(TV)
应将Remote类作为TV类的一个友元
friend class Remote; // 在TV中的任意部分无关紧要
这样Remote类就可以访问TV类的私有成员了
友元成员函数
在Tv中声明:
friend void Remote::set_chan(Tv & t, int c); // 由Tv类决定谁是它的友元
提前声明Remote类:
class Remote; class Tv;
当Tv类中存在Remote友元函数时, 应当看见Remote类声明和友元函数的类声明。
解决方法:类声明中只放方法声明, 具体定义放在后面。
内联函数使用inline关键字:
内联函数的链接是内部的, 这意味着函数定义必须在使用函数的文件中
如果放在实现文件中, 不应当使用inline关键字, 这样函数的链接是外部的。
friend class Remote已经指出Remote是一个类, 无需向前声明。
其他友元关系
相互之间的友元
共同的友元
当函数需要访问两个类的私有数据时, 它可以是一个类的友元, 同时是另一个类的友元。有时同时将函数看作两个类的友元更合理。
例:Probe类和Analyzer类
15.2 嵌套类
在C++中, 可以将类声明放在另一个类中, 在另一个类中声明的类被称为嵌套类, 它通过提供新的类型类作用域来避免名称混乱。
包含类的成员函数可以创建和使用被嵌套类的对象。
仅当声明位于公有部分, 才能在包含类的外面使用嵌套类, 而且必须使用作用域解析符。
嵌套和包含并不相同。 包含意味着类对象作为另一个类的成员, 而嵌套不创建类成员, 只是定义了一种类型, 该类型仅在包含嵌套类声明的类中才有效。
结构是一种其成员在默认情况下公有的类。
C++11 可以使用nullptr表示NULL
如果想在方法文件中使用嵌套了, 应使用两次作用域解析符:
Queue::Node::Node(const item & i) : item(i), next(0);
嵌套类和访问权限
1.嵌套类的声明决定了嵌套类的作用域, 即决定了程序的哪些部分可以创建这种类的对象。
2.和其他类一样, 嵌套类的公有、保护、私有部分控制了对类成员的访问。
作用域
如果嵌套类是在另一个类中的私有部分声明的, 则之有后者知道它。对于程序的其他部分,嵌套类都是不可见的(包括派生类)。
如果嵌套类是在另一个类中的保护部分声明的, 则他对于后者来说是可见的, 但是对于外部世界是不可见的, 但是对于派生类是可见的。
如果嵌套类是在另一个类中的公有部分声明的, 则所有部分都可以使用它。 外部程序使用时必须包含类限定符。
嵌套结构和枚举的作用域与此相同。
第十七章将更全面地介绍公有枚举。(提供可供客户程序员使用的类常数)
访问控制
对嵌套类访问权的控制规则与常规类相同。
公有、保护、私有、友元
模板中的嵌套
定义类模板时, 不会因为包含嵌套出现问题。
举例:Queue
15.3 异常
调用abort()
返回错误码
异常机制
try {} catch (异常类型) {} throw 异常类型的变量/对象;
捕获所有异常:
catch (...) {}
将对象作为异常类型
异常规范和C++11
栈解退
try块中的函数调用了引发异常的函数, 程序将从引发异常的函数跳转的try块和处理程序的函数, 涉及到栈解退。
栈解退将会释放throw到达try块前栈中所有的函数和对象(调用析构函数)。
其他异常特性
throw 引发的异常是临时拷贝。
捕获父类的引用, 可以将子类的也一起捕获, 所以父类应该放在最后。
exception类
异常、类和继承
异常何时会迷失方向
未捕获异常:
terminate()-->abort()
set_terminate()修改terminate调用的函数
意外异常:
unexpected()-->terminated()-->abort()
set_unexpected()修改unexpected()调用的函数
意外异常较为复杂。。如果unexpected()调用的函数引发了一个新的异常... ... P640
有关异常的注意事项
缺点:
使用异常增加程序代码, 降低程序运行速度, 异常规范不适用于模板, 异常和动态内存分配并不总能协同工作。
动态内存分配和异常:
栈解退时, 栈中的自动变量会被释放, 但是动态分配的内存将不会被释放。--内存泄漏。
可以在引发异常的函数中捕获该异常, 然后再catch块中包含一些清理代码, 然后重新引发该异常:
double *ar = new double[n]; try { if (oh_no) throw exception(); } catch (exception &ex) { delete [] arr; throw; }
但是, 这件共增加疏忽和产生其他错误的机会。另一种解决办法:第十六章将讨论智能指针模板。
15.4 RTTI
RTTI是运行阶段类型识别(Runtime Type Identification)的简称。
这是新添加到C++中的特性之一, 很多老实现先不支持。
RTTI旨在为程序再运行阶段确定对象的类型提供一种标准的方式。
创建一种RTTI语言标准将使得未来的各种厂商的库可以彼此兼容。
RTTI用途
C++有三个支持RTTI的元素。
1.dynamic_cast运算符将使用一个指向基类的指针来生成一个指向派生类的指针;否则, 该运算符返回0——空指针。
2.typeid运算符返回一个指出对象类型的值。
3.type_info结构储存了有关特定类型的信息。
只能将RTTI用于包含虚函数的类层次结构, 原因在于只有对于这种类层次结构, 才应该将派生类对象的地址赋给基类指针。
dynamic_cast运算符
它不能回答指针指向的是那类对象, 但能回答是否可以安全地将对象的地址赋给特定类型的指针。
语法:
dynamic_cast<Type *>(pt);
Superb * pm = dynamic_cast<Superb *>(pg);
P646-注意:即使编译器支持RTTI, 在默认情况下, 他也可能关闭该特性。如果该特性被关闭, 程序可能仍通过编译, 但将出现运行阶段错误。 在这种情况下, 您应查看文档或菜单选项。
应尽可能使用虚函数, 而只在必要时使用RTTI。
也可以将dynamic_cast用于引用, 用法稍微不同:没有与空指针对应的引用值, 因此无法使用特殊的引用之来指示失败。当请求不正确时, dynamic_cast将引发bad_cast异常, 从exception派生而来, 头文件:<typeinfo>:
#include <typeinfo> ... try { Surperb & rs = dynamic_cast<Surper &>(rg); ... } catch (bad_cast &) { ... }; // 此处应该没有分号P646
typeid运算符和type_info类
typeid运算符能够确定两个对象是否为同类型, 他与sizeof有些像, 可以接受两种参数:
类名、结果为对象的表达式。
返回值:返回一个对type_info对象的引用, type_info在头文件<typeinfo>中。
type_info重载了 == 和 != 运算符, 以便可以使用这些运算符来对类型进行比较。
typeid(Magnificent) == typeid(*pg)
如果pg是一个空指针, 则引发bad_typeid异常, 从exception中派生而来。头文件:<typeinfo>
type_info随厂商而异, 包含name()成员, 该函数返回一个随实现而异的字符串:通常是类的名称。
误用RTTI的例子
提示:如果发现在扩展的if else语句系列中使用了typeid, 则应考虑是否应该使用虚函数和dynamic_cast。
15.5 类型转换运算符
C语言中类型转换过于松散, 允许进行无意义的类型转换。
C++采取更严格地限制允许转换的类型, 并添加四个类型转换运算符, 使转换过程更规范:
dynamic_cast
const_cast
static_cast
reinterpret_cast
dynamic_cast运算符通用语法:
dynamic_cast < type-name > (expression)
const_cast运算符执行只有一种用途的类型转换, 即改变值为const或volatile, 语法与dynamic_cast相同:
const_cast < type-name > (expression)
如果类型的其他部分被修改, 上述类型转换将出错。也就是说, 除了const或volatile特征, 可以不同外, type-name和expression的类型必须相同。
使用通用转换也可以, 但是_const_cast更安全
例:必须将指向该空间的地址的const都去掉, 否则依然无法修改该空间的值。
static_cast运算符语法和前面相同:
static_cast < type-name > (expression)
仅当type-name可以被隐式转换为expression时或expression可以被隐式转换位type-name时才是合法的。
可以进行基类和派生类的向上向下转换。
可以对枚举和整型进行转换。
其他int/double/float....
reinterpret_cast运算符用于危险的类型转换。语法相同:
reinterpret_cast < type-name > (expression)
例:
struct dat {short a; short b;}; long value = 0xA224B118; dat * pd = reinterpret_cast< dat * > (&value); cout << hex << pd->a;
通常这样的转换依赖于底层编程技术, 是不可移植的。
reinterpret_cast并不支持所有的类型转换, 例如:指针类型可以转换位足以存储指针表示的整型, 但不能见指针转换为更小的整型或浮点型。不能将函数指针转换为数据指针。
C++普通类型转换也受到限制。

浙公网安备 33010602011771号