一、异常
异常类型
程序的错误大致可以分为三种,分别是语法错误、逻辑错误和运行时错误:
1)、语法错误在编译和链接阶段就能发现,只有 100% 符合语法规则的代码才能生成可执行程序。语法错误是最容易发现、最容易定位、最容易排除的错误,程序员最不需要担心的就是这种错误。
2) 、逻辑错误是说我们编写的代码思路有问题,不能够达到最终的目标,这种错误可以通过调试来解决。
3) 、运行时错误是指程序在运行期间发生的错误,例如除数为 0、内存分配失败、数组越界、文件不存在等。C++ 异常(Exception)机制就是为解决运行时错误而引入的。
二、处理异常的方式
1、abort()函数
头文件:stdlib.h或cstdlib
功 能: 异常终止一个进程。中止当前进程,返回一个错误代码
用 法: void abort(void);
#include <iostream>
#include <cstdlib>
using namespace std;
void Divid(int y)
{
if (y == 0)
abort();
cout << "Over" << endl;
}
int main()
{
Divid(0);
return 0;
}
2、返回错误码
#include <iostream>
using namespace std;
bool Divid(int y)
{
if (y == 0)
return true; //
else
return false;
}
int main()
{
if (Divid(0))
cout << "true" << endl;
else
cout << "false" << endl;
return 0;
}
3、异常机制
1)、一个catch匹配
异常处理流程分三个部分:
抛出(Throw)–> 检测(Try) --> 捕获(Catch)
异常必须显式地抛出,才能被检测和捕获到;如果没有显式的抛出,即使有异常也检测不到。使用 throw 关键字来显式地抛出异常,它的用法为:
throw exceptionData;
exceptionData 是“异常数据”的意思,它可以包含任意的信息,完全由程序员决定。exceptionData 可以是 int、float、bool 等基本类型,也可以是指针、数组、字符串、结构体、类等复合类型
#include <iostream>
using namespace std;
double division(int a, int b)
{
if( b == 0 )
{
throw "Division by zero condition!";
}
return (a / b);
}
int main ()
{
int x = 50;
int y = 0;
double z = 0;
try
{
z = division(x, y);
cout << z << endl;
}
catch (const char* msg)
{
cout << msg << endl;
}
return 0;
}
输出:
Division by zero condition!
2)、多级catch匹配
一个 try 后面可以跟多个 catch
格式:
try{
//可能抛出异常的语句
}catch (exception_type_1 e){
//处理异常的语句
}catch (exception_type_2 e){
//处理异常的语句
}
//其他的catch
catch (exception_type_n e){
//处理异常的语句
}
示例:
#include <iostream>
#include <string>
using namespace std;
class Base{ };
class Derived: public Base{ };
int main()
{
try
{
throw Derived(); //抛出自己的异常类型,实际上是创建一个Derived类型的匿名对象
cout<<"This statement will not be executed."<<endl;
}
catch(int)
{
cout<<"Exception type: int"<<endl;
}
catch(char *)
{
cout<<"Exception type: cahr *"<<endl;
}
catch(Base) //匹配成功(向上转型)
{
cout<<"Exception type: Base"<<endl;
}
catch(Derived)
{
cout<<"Exception type: Derived"<<endl;
}
return 0;
}
输出:
Exception type: Base
3)、catch 在匹配过程中的类型转换
catch 在匹配异常类型的过程中,也会进行类型转换,但是这种转换受到了更多的限制,仅能进行「向上转型」、「const 转换」和「数组或函数指针转换」,其他的都不能应用于 catch
#include <iostream>
using namespace std;
int main()
{
int nums[] = {1, 2, 3};
try
{
throw nums;
cout << "This statement will not be executed." << endl;
}
catch(const int *)
{
cout << "Exception type: const int *" << endl;
}
return 0;
}
输出:
Exception type: const int *
4)、发生异常的位置
异常可以发生在当前的 try 块中,也可以发生在 try 块所调用的某个函数中,或者是所调用的函数又调用了另外的一个函数,这个另外的函数中发生了异常。这些异常,都可以被 try 检测到。
<1>异常发生在当前的 try 块中
#include <iostream>
#include <string>
using namespace std;
int main()
{
try
{
throw "Unknown Exception"; //抛出异常
cout << "This statement will not be executed." << endl; //抛出异常时这条语句不会被执行
}
catch(const char* e)
{
cout << e << endl;
}
return 0;
}
输出:
Unknown Exception
<2>try 块中调用的某个函数中发生了异常
#include <iostream>
#include <string>
#include <exception>
using namespace std;
void func()
{
throw "Unknown Exception"; //抛出异常
cout << "[1]This statement will not be executed." << endl;
}
int main()
{
try
{
func();
cout<<"[2]This statement will not be executed."<<endl;
}
catch(const char* &e)
{
cout<<e<<endl;
}
return 0;
}
输出:
Unknown Exception
<3>try 块中调用了某个函数,该函数又调用了另外的一个函数,这个另外的函数抛出了异常
#include <iostream>
#include <string>
#include <exception>
using namespace std;
void func_inner()
{
throw "Unknown Exception"; //抛出异常
cout<<"[1]This statement will not be executed."<<endl;
}
void func_outer()
{
func_inner();
cout<<"[2]This statement will not be executed."<<endl;
}
int main()
{
try
{
func_outer();
cout<<"[3]This statement will not be executed."<<endl;
}
catch(const char* &e)
{
cout<<e<<endl;
}
return 0;
}
输出:
Unknown Exception
发生异常后,程序的执行流会沿着函数的调用链往前回退,直到遇见 try 才停止。在这个回退过程中,调用链中剩下的代码(所有函数中未被执行的代码)都会被跳过,没有执行的机会了
5)、异常规范
throw 关键字除了可以用在函数体中抛出异常,还可以用在函数头和函数体之间,指明当前函数能够抛出的异常类型,这称为异常规范(Exception specification)
double func (char param) throw (); //不抛出任何类型的异常
double func (char param) throw (int); //抛出int类型的异常
double func (char param) throw (int, char, double); //会抛出多种类型的异常
<1>虚函数中的异常规范
派生类虚函数的异常规范必须与基类虚函数的异常规范一样严格,或者更严格。只有这样,当通过基类指针(或者引用)调用派生类虚函数时,才能保证不违背基类成员函数的异常规范
class Base
{
public:
virtual int fun1(int) throw();
virtual int fun2(int) throw(int);
virtual string fun3() throw(int, string);
};
class Derived:public Base
{
public:
int fun1(int) throw(int); //错!异常规范不如 throw() 严格
int fun2(int) throw(int); //对!有相同的异常规范
string fun3() throw(string); //对!异常规范比 throw(int,string) 更严格
}
<2>异常规范与函数定义和函数声明
C++ 规定,异常规范在函数声明和函数定义中必须同时指明,并且要严格保持一致,不能更加严格或者更加宽松
//错!定义中有异常规范,声明中没有
void func1();
void func1() throw(int) { }
//错!定义和声明中的异常规范不一致
void func2() throw(int);
void func2() throw(int, bool) { }
//对!定义和声明中的异常规范严格一致
void func3() throw(float, char*);
void func3() throw(float, char*) { }
请抛弃异常规范,不要再使用它
<3>noexcept
noexcept是特殊的异常规范,指出函数不会引发异常
int add(int a, int b) noexcept; //该函数不会引发异常
4、将对象用作异常类型
引发异常的函数传递一个对象时,有以下优点:
1)、使用不同的异常类型来区分不同的函数在不同的情况下引发的异常;
2)、对象可以携带信息,可以根据这些信息来确定引发异常的原因;同时catch块可以根据这些信息来决定采取什么样的措施
#include <iostream>
using namespace std;
class BadDivision
{
private:
double m_a;
double m_b;
public:
BadDivision(double a, double b)
{
m_a = a;
m_b = b;
}
void mesg()
{
cout << "invalid argument" << endl;
}
};
double division(double a, double b)
{
if (b == 0)
throw BadDivision(a, b); //抛出对象
return a / b;
}
int main()
{
double a = 10;
double b = 0;
try
{
division(a, b);
}
catch (BadDivision &e) //捕捉对象
{
e.mesg();
}
return 0;
}
输出:
invalid argument
5、exception类
C++语言本身或者标准库抛出的异常都是 exception 的子类,称为标准异常(Standard Exception)。可以通过下面的语句来捕获所有的标准异常:
try
{
//可能抛出异常的语句
}
catch(exception &e) //使用引用,是为了提高效率。如果不使用引用,就要经历一次对象拷贝(要调用拷贝构造函数)的过程
{
//处理异常的语句
}
1)、exception类声明
exception 类位于 头文件中,它的声明为:
class exception
{
public:
exception () throw(); //构造函数
exception (const exception&) throw(); //拷贝构造函数
exception& operator= (const exception&) throw(); //运算符重载
virtual ~exception() throw(); //虚析构函数
virtual const char* what() const throw(); //虚函数
}
2)、exception 类的继承层次:
#include <iostream>
#include <exception>
using namespace std;
class MyException : public exception
{
public:
const char * what () const throw ()
{
return "C++ Exception";
}
};
int main()
{
try
{
throw MyException();
}
catch(MyException& e)
{
cout << "MyException caught" << endl;
cout << e.what() << endl;
}
catch(exception& e)
{
//其他的错误
}
}
输出:
MyException caught
C++ Exception
三、RTTI
1、RTTI是什么
运行阶段类型识别(Runtime Type Identification)的简称。这是新添加到c++中的特性之一。
RTTI只能用于包含虚函数的类。
2、RTTI的作用
通过运行时类型信息程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型
3、RTTI的用途
c++有3个支持RTTI的元素
1)、dynamic_cast 运算符:使用一个指向基类的指针来生成一个指向派生类的指针;否则,该运算符返回0——空指针
2)、typeid运算符:返回结果名为type_info的标准库类型的对象的引用(在头文件typeinfo中定义)
3)、type_info结构:存储了有关特定类型的信息
1)、dynamic_cast
#include <iostream>
#include <typeinfo>
using namespace std;
class A
{
public:
virtual void Print() { cout<<"This is class A."<<endl; }
};
class B
{
public:
virtual void Print() { cout<<"This is class B."<<endl; }
};
class C : public A, public B
{
public:
void Print() { cout<<"This is class C."<<endl; }
};
int main()
{
A *pA = new C;
//C *pC = pA; // Wrong 编译器会提示错误
C *pC = dynamic_cast<C *>(pA);
if (pC != NULL)
{
pC->Print();
}
delete pA;
}
输出:
This is class C.
2)、typeid运算符
对于c++的内置数据类型或自定义数据类型,使用typeid可以输出它们的数据类型
#include <iostream>
#include <typeinfo>
using namespace std;
int main()
{
int i = 10;
char ch = 'a';
cout << typeid(i).name() << endl; // int
cout << typeid(ch).name() << endl; // char
return 0;
}
输出:
i
c
#include <iostream>
#include <typeinfo>
using namespace std;
class A
{
public:
void Print() { cout<<"This is class A."<<endl; }
};
class B : public A
{
public:
void Print() { cout<<"This is class B."<<endl; }
};
struct C
{
void Print() { cout<<"This is struct C."<<endl; }
};
int main()
{
A *pA1 = new A();
A a2;
cout << typeid(pA1).name() << endl; // class A *
cout << typeid(a2).name() << endl; // class A
B *pB1 = new B();
cout << typeid(pB1).name() << endl; // class B *
C *pC1 = new C();
C c2;
cout << typeid(pC1).name() << endl; // struct C *
cout << typeid(c2).name() << endl; // struct C
return 0;
}
输出:
P1A
1A
P1B
P1C
1C
3)、type_info
#include<iostream>
#include<typeinfo>
using namespace std;
int main(void)
{
int i = 1;
float f = 1.222;
//定义引用对象
const type_info& t1 = typeid(f);
const type_info& t2 = typeid(int);
cout << " ti.name() = " << t1.name() << endl;
return 0;
}
输出:
ti.name() = f
参考&致谢:
1、《C++ Primer Plus》
2、C语言中文网
3、菜鸟教程
4、C++中的RTTI机制