CPP 15. 友元、异常和其他 2020-11-29 浏览量 720 暂无评论 # 第15章 友元、异常和其他 - 友元类 - 友元类方法 - 嵌套类 //上述都是在其他类中声明的类 - 引发异常、try块和catch块 - 异常类 - 运行阶段类型识别(RTTI) - dynamic_cast和typeid - static_cast、const_cast和reiterpret_cast ## 15.1 友元类 友元类适用于两个类既不是继承关系,又不是包含关系,但两个类又有关系的情况,如遥控器和电视机。 ```cpp // tv.h #ifndef TV_H_ #define TV_H_ class Tv { public: friend class Remote; // Remote可以使用Tv的私有成员 enum {Off, On}; enum {MinVal, MaxVal = 20}; enum {Antenna, Cable}; enum {TV, DVD}; Tv(int s = Off, int mc = 125) : state(s), volume(5), maxchannel(mc), channel(2), mode(Cable), input(TC) {} void onoff() {state = (state == On) ? Off : On;} // void onoff() {state ^= 1;} bool ison() const {return state == On;} bool volup(); bool voldown(); void chanup(); void chandown(); void set_mode() {mode = (mode == Antenna) ? Cable : Antenna;} void set_input() {input = (input == TV) ? DVD : TV;} void setting() const; private: int state; int volume; int maxchannel; int channel; int mode; int input; }; class Remote { ... }; ... ``` **友元成员函数:** 仅让特定的类成员成为另一个类的友元。 ```cpp // 前向声明,因为Remote的方法提到了Tv对象,且Tv对象中也有Remote方法的声明 // 在Tv类的声明中看到Remote的一个方法被声明为Tv类的友元之前,应该先看到Remote类 // 的声明和set_chan()方法的声明;同理,Remote类声明中不要有内联代码,将方法的 // 定义放在Tv类声明之后,并使用关键字inline使其成为内联方法 class Tv; class Remote {...}; class Tv { friend void Remote::set_chan(Tv & t, int c); // 这样Remote类的set_chan成员就可以访问Tv类的私有成员 }; ``` **其他友元关系:** 两个类互为友元 对于使用Remote对象的Tv方法,其原型可在Remote类声明之前声明,但必须在Remote类声明之后定义。 ```cpp class Tv { friend class Remote; public: void buzz(Remote & r); ... }; class Remote { friend class Tv; public: void Bool volup(Tv & t) {t.volup();} ... }; inline void Tv::buzz(Remote & r) { ... } ``` **共同的友元:** ```cpp // 前向声明 class Analyzer; class Probe { friend void sync(Analyzer & a, const Probe & p); friend void sync(Probe & p, const Analyzer & a); ... } class Analyzer { friend void sync(Analyzer & a, const Probe & p); friend void sync(Probe & p, const Analyzer & a); ... }; inline void sync(Analyzer & a, const Probe & p) { ... } inline void sync(Probe & p, const Analyzer & a) { ... } ``` ## 15.2 嵌套类 在另一个类中声明的类被称为嵌套类(nested class)。包含类的成员函数可以创建和使用被嵌套类的对象,而仅当声明位于共有部分,才能在包含类的外面使用嵌套类,而且必须使用作用域解析运算符。 什么时候需要使用嵌套类:通常是为了帮助实现另一个类,并避免名称冲突,使用类来存放变量可以利用类构造函数方便地赋值。 #### 15.3 异常 1. throw语句将控制权向上返回到第一个这样的函数:包含能够捕获相应异常的try-catch组合。 2. 引发异常时编译器总是创建一个临时拷贝。 3. catch块的排序顺序应该与异常类派生顺序相反,因为引发的异常对象将被第一个与之匹配的catch块捕获。 ![](http://lcipm.com/Jineng/picture-bed/uploads/2c3fcc85d5010595420028c5c417529e/20201128223836.png) 4. 可以使用省略号来表示异常类型,从而捕获任何异常。 ```cpp #include std::abort(); // 发送消息:abnormal program termination,终止程序 std::exit(); // 不发送消息,终止程序 // 更希望遇到异常时进行一些处理,而不是直接终止程序,这时候可以返回错误码或者 // throw引发异常并用catch捕获异常,try后面可以跟好几个catch与对应异常类型匹配 // 如果没找到对应的catch,默认异常终止 // 程序进行栈解退以回到能够捕获异常的地方时,将释放栈中动存储型变量。 // 如果变量是类对象,将为该对象调用析构函数。 #include double hmean(double a, double b); int main() { double x, y, z; std::cout << "Enter two numbers: "; while (std::cin >> x >> y) { try { z = heamn(x, y); } catch (const char * s) // 与字符串异常匹配 { std::cout << s << std::endl; std::cout << "Enter a new pair of numbers: "; continue; } std::cout << "Harmonic mean of " << x << " and " << y << " is " << z << std::endl; std::cout << "Enter next set of numbers. : "; } std::cout << "Bye!\n"; return 0; } double hmean(double a, double b) { if (a == -b) throw "some description"; return 2.0 * a * b / (a + b); } ``` **exception类** 可以将exception类用作异常的基类,里面有个名为what()的虚拟成员函数,它返回一个字符串。 一些基于exception的异常类型。 1. stdexcept异常类 这些类中都有一个可以为what()提供返回字符串的构造函数。 两个派生类系列的基类: a. logic_error系列:可通过编程修复 - domain_error:定义域 - invalid_argument:有效参数 - length_error:空间 - out_of_bounds:索引错误 b. runtime_error系列:存在无法避免的问题 - range_error: - overflow_error:上溢 - underflow_error:下溢 2. bad_alloc异常和new 当使用new出现内存分配问题时,以前是返回一个空指针,现在是让new引发bad_alloc异常。 3. 空指针和new 为了兼容以前的返回空指针的new ```cpp int * pi = new (std::nothrow) int; ``` 异常的意外情况: 意外异常:不与规范列表中的某种异常匹配,先调用unexpected()函数,这个函数将调用terminate()函数,可修改,set_unexpected(func_name) 未捕获异常:不会导致程序立刻终止,而是先调用函数terminate(),默认情况下,terminate()调用abort()函数,可修改,set_terminate(func_name) 可以设计一个替代函数,将意外异常转换为bad_exception异常 ```cpp #include using namespace std; void myUnexpected() { throw std::bad_exception(); // 仅使用throw,而不指定异常将导致重新引发原来的异常 } ``` ## 15.4 RTTI(Runtime Type Identification) 为什么会出现RTTI?想知道类型,那为什么要知道类型呢?1. 派生对象可能包含不是继承而来的方法,只有某些类型的对象可以使用该方法;2. 想跟踪生成的对象的类型。 **注意:只能将RTTI用于包含虚函数的类层次结构,因为只有对于这种类层次结构,才应该将派生对象的地址赋给基类指针。** 如何支持RTTI? 1. 如果可能的话,dynamic_cast运算符,使用一个指向基类的指针来生成一个指向派生类的指针,否则,该运算符返回0--空指针; ```cpp // 指针pg的类型是否可以被安全地转换为Superb *? // 可以的话,就返回对象的地址 // 不可以的话,返回空指针 Superb * pm = dynamic_cast(pg); ``` 2. typeid返回一个指出对象的类型的值; ```cpp // 头文件typeinfo // typeid运算符使得能够确定两个对象是否为同种类型 // 接受两种参数:类名;结果为对象的表达式 // 返回一个对type_info对象的引用,包含一个name()成员 typeid(Magnificent) == typeid(*pg) // 如果pg是一个空指针,程序将引发bad_typeid异常 ``` 3. type_info结构存储了有关特定类型的信息 ## 15.5 类型转换运算符 ![](http://lcipm.com/Jineng/picture-bed/uploads/674f7007b4be6d7e7498d4d8eeb444aa/20201129214253.png) 🐸:给爷看笑了。 出现的原因:C语言中的类型转换运算符太过松散。 4个类型转换运算符:dynamic_cast; const_cast; static_cast; reinterpret_cast。 1. dynamic_cast ```cpp // 假设High和Low是两个类,ph和pl的类型分别为High*和Low* // 则仅当Low是High的可访问基类时,下面的语句才将一个Low*指针赋给pl // 这个指针指向谁? pl = dynamic_cast ph; ``` 2. const_cast ```cpp // 用于执行只有一种用途的类型转换,即const或volatile const_cast (expression) // 除了const或volatile特征可以不同外,type_name和expression的类型必须相同 High bar; const High * pbar = &bar; High * pb = const_cast (pbar); // 使*pb成为一个可用于修改bar对象值的指针 // 只有当指向的值不是const时才可行 ``` 3. static_cast ```cpp // 仅当type_name可被隐式转换为expression所属的类型或反过来时,转换才是合法的 static_cast (expression) ``` 4. reinterpret_cast ```cpp // 用于天生危险的类型转换 struct dat {short a; short b;}; long value = 0xA224B11B; dat * pd = reinterpret_cast (&value); cout << hex << pd->a; // 显示value的前2个字节 ``` 赞赏 微信支付 支付宝支付