85.异常(1)异常处理机制
异常:运行错误(比如无法打开文件,动态内存申请失败),导致程序无法继续正常运行;
处理异常的常用方法:
1.调用stdlib中的abort()函数:输出一个程序异常终止的信息,然后终止程序;
void abort(void)
用于中止程序执行,直接从调用的地方跳出;
2.利用函数的返回值报告错误;
其中,DBL_MAX
是double型的最大值。
示例:
异常机制
遇到异常时,将控制权从程序的一个部分转移到另一个部分;
异常处理的组成部分
try块:用于标识可能抛出异常的程序块;
throw抛出异常:程序检测到异常时,跳出当前部分;
catch捕获异常:处理异常的程序;
示例:
将对象用作异常类型
throw可以抛出任何类型的值,catch捕获抛出值的类型(我们可以用不同的类型标识不同的错误)
我们可以设计一个简单的异常类型:
用异常类型区分不同的错误
如果程序中用到多个会抛出异常的函数,可以通过抛出的类型确定不同的处理过程:
86.异常(2)exception类
exception类
对于上一节中,用异常类型区分不同的错误,需要重复写catch,现在我们可以使用C++提供的标准类exception类,用作其他异常类的基类,其中有一个返回字符串的虚函数what(),用于返回出错的原因(利用了C++的多态性)
现在对于不同的异常类型,只需要一个catch就能调用不同的成员(利用了多态性),取决于try内部的操作抛出哪个异常类,但在catch中都是用基类去指向派生类。
bad_alloc异常类
对于异常类,除了exception基类,C++还有一个bad_alloc异常类:
- 申请动态变量失败时,早期的C++返回一个空指针,现在的C++则是抛出bad_alloc异常类,bad_alloc从exception派生,提供了what()
87.RTTI(1)
RTTI:运行阶段类型识别(Runtime Type Identification)
RTTI用途:
1.跟踪对象类型;
2.在继承结构中确定类型转换的安全性;
支持RTTI的元素
1.typeid运算符返回一个type_info类类型的值,type_info存储了有关特定类型的信息;
2.用dynamic_cast检查继承层次中基类指针或引用向派生类转换的合法性;
typeid运算符和type_info类
运算符typeid:获取对象的类型信息,是一元运算符
运算对象:类名 或 结果为对象的表达式
返回值:type_info对象的引用
type_info类:有一个成员函数name(),返回类或者对象对应的类名
示例
88.RTTI(2)
dynamic_cast运算符:用于检查是否可以安全将基类地址转换成派生类地址。
直接进行类型强制转换,编译器不会检查,会造成后期的内存读写错误,应该使用RTTI,如果返回空指针,对程序的后续行为执行才是安全的。
89.类型转换运算符
C语言风格的强制类型转换:(类型名) 表达式
比如:
int a=5;
char* p=(char*) &a;
int* pi=(int*) p;
C风格的强制类型转换缺点:没有检查或指明所做转换的意义。
C++严格规范转换限制,使得转换过程更加规范,并设置了4个类型转换运算符:
- dynamic_cast:用于检查是否可以安全将基类地址转换成派生类地址。
- const_cast
- static_cast
- reinterpret_cast
const_cast:删除对象的常量性质
输出结果为:
可以发现,其实const修饰的常量并没有被修改(因为常量在内存的不可变区域),const_cast的用途其实是:
- 当调用了一个形式参数不是const的函数,但实际参数是const的,但是我们确定这个函数不会对参数做修改,因此需要const_cast去除实际参数的const限定,以便函数能够接受这个实际参数。
static_cast:编译器支持的隐式转换(自动类型转换)
比如:
double x=1.998;
int a;
a=static_cast<int> x;
可以看出,static_cast是安全的转换,只是会丢失精度;
C语言自动类型转换出现的场合
1.赋值或初始化时,如果右边的表达式计算结果类型与左边的变量类型不同:右边值被转换成左边变量类型;
2.函数参数传递时,实际参数被转换成形式参数的类型;
3.表达式中的运算数类型不一致,需要遵循转换规则:把精度小的运算数向精度大的运算数转换;
reinterpret_cast:危险的转换,重新解释地址中的内容
比如:
reinterpret_cast这种转换看起来是没有意义的(因为两个变量之间没有关联关系),这种转换更多用在输入输出场景(把内存中的一块空间内容完整写入一个文件中)
C++相比C,增加了类型转换运算符,一是为了让编译器检查类型转换的合理性,二是为了让读代码的程序员看到当前类型转换的意义。
90.string类
对于C风格字符串:
用字符数组存储
无法用运算符进行操作
执行字符串操作时需要特别注意空间问题
C++中专门处理字符串的类,在库string中,用动态数组存储(动态数组可以回顾20.数组的替代品中的vector,注意到另一个替代品array类可以回顾78.类模板(2)非类型参数),必要时会自动扩大空间
功能:
- 不同的构造方法
- 字符串赋值(=)
- 字符串连接(+)
- 字符串比较(>, <, >=, <=, ==, !=)
- 字符串输入输出(>>, <<)
- 用下标访问字符串的元素
示例:
91.智能指针
问题引入
上述函数存在内存泄漏的可能:当抛出异常时,局部变量ps的空间被释放,但ps指向的动态变量没有释放。
解决方案是使用智能指针;
智能指针:可以在释放指针本身的空间时候,调用它指向的对象的析构函数,从而析构该指针指向的对象。
智能指针是一个类模板,模板参数是智能指针指向的对象类型。智能指针包含在库memory中。常用的智能指针有3种:
- auto_ptr:C++98提出
- unique_ptr:C++11提出
- shared_ptr:C++11提出
智能指针示例
3种智能指针的区别:多个指针指向同一个对象时,3种指针有不同的行为
auto_ptr:在赋值时,会将右边指针的控制权转移给左边指针,右边的指针就失去了控制权(自动指向NULL),这种情况下,对右边指针进行读写操作会出错;
unique_ptr:明确指出不允许共享,当发生指针赋值时,编译出错;
shared_ptr:允许多个指针指向同一个对象,析构时,只有最后一个析构的指针会调用对象的析构函数;
auto_ptr示例
unique_ptr示例
shared_ptr示例
注意到85节中的抛出异常类,throw bad_hmean(a, b);
语句,bad_hmean是一个类类型;
在91节智能指针中,出现auto_ptr<string> ps (new string("sentence"))
和auto_ptr<string> (new string("sentence"))
这种语句;
这些类的实例化方式,可以回顾53.对象构造中的内容
throw bad_hmean(a, b);
中的bad_hmean(a, b)
是构造函数的显式调用;auto_ptr<string> ps (new string("sentence"))
是构造函数的隐式调用(定义对象时自动调用);auto_ptr<string> (new string("sentence"))
是构造函数的显式调用。