8.异常
8.1程序异常
程序在运行阶段遇到错误,而导致程序无法继续正常的执行下去,称为异常。针对异常,处理方法之一是调用abort()函数、exit()函数使程序中止运行。
#include <iostream>
#include <cstdlib>
using namespace std;
int max(int a, int b);
int main(int argc,char ** argv)
{
int a,b;
cout << "input a and b:";
cin >>a>> b;
int c = max(a,b);
cout << "最大值是" <<c<< endl;
return 0;
}
int max(int a,int b)
{
if (a > b)
return a;
else if (a < b)
return b;
else
{
cout << "a = b" << endl;
abort();
//exit(0);
}
}
运行结果:
(1)使用abort
(2)使用exit
input a and b:3 3
a = b
代码解释:(1)abort函数:调用时,未做任何清理工作,直接中止程序。(2)exit函数:exit(0)表示正常执行并退出程序,exit(0)表示非正常执行并退出程序;在程序退出时,会清理全局对象和静态对象,关闭I/O通道,但不会清理局部对象。
因此在使用abort函数,全局对象和局部对象都不析构,exit函数则是局部对象不析构。类对象的不析构可能造成内存泄漏(用new申请的类数据成员),因此尽量不要使用abort和exit函数。
8.2 C++异常机制
8.2.1简单异常
#include <iostream>
using namespace std;
int max(int a, int b);
int main(int argc,char ** argv)
{
int a,b;
cout << "input a and b:";
cin >>a>> b;
try {//保护的代码
int c = max(a, b);
cout << "最大值是" << c << endl;
}
catch (const char * msg)//捕捉异常并处理
{
cout << msg << endl;
}
return 0;
}
int max(int a,int b)
{
if (a > b)
return a;
else if (a < b)
return b;
else
throw "a equals b";//扔出异常
}
运行结果:
input a and b:4 4
a equals b
关键字try后面用花括号将代码块括起,表示这些代码块可能引发异常。关键字catch后的小括号,指出要捕捉的异常类型,其中变量是throw出的数值;花括号内部是针对异常的处理代码。throw关键字表示引发异常,并向上扔出异常的数值。
8.2.2栈解退
#include <iostream>
using namespace std;
void Fun_A(int n);
void Fun_B(int n);
int main(int argc,char ** argv)
{
int i;
while (1)
{
cout << "input i:";
cin >> i;
try {//保护的代码
Fun_A(i);
}
catch (const char * msg)//捕捉异常并处理
{
cout << msg << endl;
}
catch (int i)//这里的i是catch语句内的变量,catch执行完后就销毁
{
cout << i << endl;
}
catch (double i)
{
cout << i << endl;
}
catch (float i)
{
cout << i << endl;
}
}
return 0;
}
void Fun_A(int n)
{
Fun_B(n);
}
void Fun_B(int n)
{
int a = 1;
double b = 1.2;
float c = 2.3;
if (n == 0)
throw a;
else if (n == 1)
throw b;
else if (n == 2)
throw c;
else
throw "n不等于0/1/2";
}
运行结果:
input i:0
1
input i:1
1.2
input i:2
2.3
input i:3
n不等于0/1/2
input i:
当函数Fun_B发生异常中止,则程序会释放Fun_B函数栈中的内存,但不会释放完Fun_B栈内存而停止,而是继续释放Fun_A中的栈内存,直到找到第一次出现的catch。这称为栈解退。
当函数调用层数非常深时,如A>B>C>...>Z,当函数Z出错时,若A要处理这个错误,则要一级一级返回给上一个函数,一直到函数A。这样非常的麻烦,而栈解退的机制使得函数Z可以直接跳转到函数A,函数A通过catch中的代码来处理函数Z中的错误(异常)。
8.2.3类对象
throw扔出异常类型,catch根据异常的类型来判断,而数据类型就那么几种,难道要每一个都用来throw吗?不用的,难道忘了类也是一种数据类型,我们可以throw类的对象。
#include <iostream>
using namespace std;
void Fun_A(int n);
class Person
{
public:
const char * what() const
{
return "类Person发生了一个异常";
}
};
int main(int argc,char ** argv)
{
int i;
while (1)
{
cout << "input i:";
cin >> i;
try {//保护的代码
Fun_A(i);
}
catch (const char * msg)//捕捉异常并处理
{
cout << msg << endl;
}
catch (int i)//这里的i是catch语句内的变量,catch执行完后就销毁
{
cout << i << endl;
}
catch (double i)
{
cout << i << endl;
}
catch (float i)
{
cout << i << endl;
}
catch (Person & per)//必须是引用,不能是指针,若是指针则数据类型是Person *
{
cout<<per.what()<<endl;
}
catch (...)//发生了其他异常,用...代替,其必须放在最后一个catch
{
cout << "other exception" << endl;
}
}
return 0;
}
void Fun_A(int n)
{
int a = 1;
double b = 1.2;
float c = 2.3;
//Person per;
if (n == 0)
throw a;
else if (n == 1)
throw b;
else if (n == 2)
throw c;
else if (n == 3)
throw Person();/*或throw per,这里调用无参构造函数,必须使用Person(),而不是Person*/
}
运行结果:
input i:0
1
input i:1
1.2
input i:2
2.3
input i:3
类Person发生了一个异常
代码解释:throw类对象,可以直接throw一个实例化的对象;或者throw Person()调用类Person的无参构造函数,若是有参构造函数,则括号内要带上参数。
8.2.4类继承
若是派生类,那么该如何catch呢?
#include <iostream>
using namespace std;
void Fun_A(int n);
class Person
{
public:
const char * what() const
{
return "类Person发生了一个异常";
}
};
class Chinese:public Person
{
public:
const char * what() const
{
return "类Chinese发生了一个异常";
}
};
int main(int argc,char ** argv)
{
int i;
while (1)
{
cout << "input i:";
cin >> i;
try {//保护的代码
Fun_A(i);
}
catch (const char * msg)//捕捉异常并处理
{
cout << msg << endl;
}
catch (int i)//这里的i是catch语句内的变量,catch执行完后就销毁
{
cout << i << endl;
}
catch (double i)
{
cout << i << endl;
}
catch (float i)
{
cout << i << endl;
}
catch (Person & per)//必须是引用,不能是指针,若是指针则数据类型是Person *
{
cout<<per.what()<<endl;
}
catch (Chinese & chi)
{
cout << chi.what() << endl;
}
catch (...)//发生了其他异常,用...代替,其必须放在最后一个catch
{
cout << "other exception" << endl;
}
}
return 0;
}
void Fun_A(int n)
{
int a = 1;
double b = 1.2;
float c = 2.3;
//Person per;
if (n == 0)
throw a;
else if (n == 1)
throw b;
else if (n == 2)
throw c;
else if (n == 3)
throw Person();//或throw per,这里调用无参构造函数,必须使用Person(),而不是Person
else if (n == 4)
throw Chinese();
}
运行结果:
input i:0
1
input i:1
1.2
input i:2
2.3
input i:3
类Person发生了一个异常
input i:4
类Person发生了一个异常
为什么输入4后,也是catch的Person异常。因为当throw一个Chinese对象的异常时,按照catch的顺序,当执行到catch(Person & per)时,将Chinese对象上行转换为Person类对象(上行转换是安全的),因此实际执行的却是Person类异常的处理代码。
#include <iostream>
using namespace std;
void Fun_A(int n);
class Person
{
public:
const char * what() const
{
return "类Person发生了一个异常";
}
};
class Chinese:public Person
{
public:
const char * what() const
{
return "类Chinese发生了一个异常";
}
};
int main(int argc,char ** argv)
{
int i;
while (1)
{
cout << "input i:";
cin >> i;
try {//保护的代码
Fun_A(i);
}
catch (const char * msg)//捕捉异常并处理
{
cout << msg << endl;
}
catch (int i)//这里的i是catch语句内的变量,catch执行完后就销毁
{
cout << i << endl;
}
catch (double i)
{
cout << i << endl;
}
catch (float i)
{
cout << i << endl;
}
catch (Chinese & chi)
{
cout << chi.what() << endl;
}
catch (Person & per)
{
cout << per.what() << endl;
}
catch (...)//发生了其他异常,用...代替,其必须放在最后一个catch
{
cout << "other exception" << endl;
}
}
return 0;
}
void Fun_A(int n)
{
int a = 1;
double b = 1.2;
float c = 2.3;
//Person per;
if (n == 0)
throw a;
else if (n == 1)
throw b;
else if (n == 2)
throw c;
else if (n == 3)
throw Person();//或throw per,这里调用无参构造函数,必须使用Person(),而不是Person
else if (n == 4)
throw Chinese();
}
运行结果:
input i:0
1
input i:1
1.2
input i:2
2.3
input i:3
类Person发生了一个异常
input i:4
类Chinese发生了一个异常
解决方法也很简单,就是将派生类的catch语句放到基类的catch语句前面。
每次都要分别对派生类对象和基类对象catch两次,能不能只catch一次呢?有2种方法,方法1:虚函数。
#include <iostream>
using namespace std;
void Fun_A(int n);
class Person
{
public:
virtual const char * what() const
{
return "类Person发生了一个异常";
}
};
class Chinese:public Person
{
public:
const char * what() const
{
return "类Chinese发生了一个异常";
}
};
int main(int argc,char ** argv)
{
int i;
while (1)
{
cout << "input i:";
cin >> i;
try {//保护的代码
Fun_A(i);
}
catch (const char * msg)//捕捉异常并处理
{
cout << msg << endl;
}
catch (int i)//这里的i是catch语句内的变量,catch执行完后就销毁
{
cout << i << endl;
}
catch (double i)
{
cout << i << endl;
}
catch (float i)
{
cout << i << endl;
}
catch (Person & per)
{
cout << per.what() << endl;
}
catch (...)//发生了其他异常,用...代替,其必须放在最后一个catch
{
cout << "other exception" << endl;
}
}
return 0;
}
void Fun_A(int n)
{
int a = 1;
double b = 1.2;
float c = 2.3;
if (n == 0)
throw a;
else if (n == 1)
throw b;
else if (n == 2)
throw c;
else if (n == 3)
throw Person();//或throw per,这里调用无参构造函数,必须使用Person(),而不是Person
else if (n == 4)
throw Chinese();
}
运行结果:
input i:0
1
input i:1
1.2
input i:2
2.3
input i:3
类Person发生了一个异常
input i:4
类Chinese发生了一个异常
方法2:类exception。
8.2.5类exception
#include <iostream>
#include <exception>
using namespace std;
void Fun_A(int n);
class Person:public exception
{
public:
const char * what() const
{
return "类Person发生了一个异常";
}
};
class Chinese:public Person
{
public:
const char * what() const
{
return "类Chinese发生了一个异常";
}
};
int main(int argc,char ** argv)
{
int i;
while (1)
{
cout << "input i:";
cin >> i;
try {//保护的代码
Fun_A(i);
}
catch (const char * msg)//捕捉异常并处理
{
cout << msg << endl;
}
catch (int i)//这里的i是catch语句内的变量,catch执行完后就销毁
{
cout << i << endl;
}
catch (double i)
{
cout << i << endl;
}
catch (float i)
{
cout << i << endl;
}
catch (exception & exc)
{
cout << exc.what() << endl;
}
catch (...)//发生了其他异常,用...代替,其必须放在最后一个catch
{
cout << "other exception" << endl;
}
}
return 0;
}
void Fun_A(int n)
{
int a = 1;
double b = 1.2;
float c = 2.3;
if (n == 0)
throw a;
else if (n == 1)
throw b;
else if (n == 2)
throw c;
else if (n == 3)
throw Person();//或throw per,这里调用无参构造函数,必须使用Person(),而不是Person
else if (n == 4)
throw Chinese();
}
运行结果:
input i:0
1
input i:1
1.2
input i:2
2.3
input i:3
类Person发生了一个异常
input i:4
类Chinese发生了一个异常
在头文件exception中,定义了exception类,C++可以把它作为其他异常类的基类。exception类中有虚函数const char * what() const,因此其派生类只需要定义自己的what函数即可;在catch处也只需要捕捉类exception的异常。
在头文件exception类中还定义了logic_error和runtime_error异常类。
logic_error异常类中描述了逻辑错误的异常,包括domain_error、invalid_error、length_error、out_of_bounds。
runtime_error异常类描述了运行期间难以预料的错误,包括range_error、overflow_error、underflow_error。
8.2.6意料之外的异常
在C++98中有一项功能:异常规范,但是在C++11被摒弃了,即C++11可以用,但是不建议用。
void Fun_A(int n) throw( )表示Fun_A函数不会扔出异常
void Fun_B(int n) throw(int,double)表示Fun_B函数只会扔出int、double异常。
在C++11中,提供了关键字noexcept俩表示函数不会扔出异常,即void Fun_A(int n) noexcept。
这些异常规范个人建议不要使用。
当throw一个异常,并没有catch语句来捕捉时,这样的异常称呼为意料之外的异常。
对于意料之外的异常,它会调用库中的terminate()函数。当然我们也可以使用set_terminate()函数来定义自己的terminate()函数。
#include <iostream>
using namespace std;
void Fun_A(int n);
void Fun_B(int n);
void my_terminate()
{
cout << "my_terminate" << endl;
}
int main(int argc, char ** argv)
{
int i;
set_terminate(my_terminate);
while (1)
{
cout << "input i:";
cin >> i;
try {//保护的代码
Fun_A(i);
}
catch (int i)
{
cout << i << endl;
}
catch (double i)
{
cout << i << endl;
}
}
return 0;
}
void Fun_A(int n)
{
Fun_B(n);
}
void Fun_B(int n)
{
int a = 1;
double b = 1.2;
float c = 2.3;
if (n == 0)
throw a;
else if (n == 1)
throw b;
else if (n == 2)
throw c;
}
运行结果:
input i:0
1
input i:1
1.2
input i:2
my_terminate
发生意料之外的异常时,程序会先调用terminate()函数,再调用abort()函数中止程序。
8.3异常的注意事项
int exception_safe(int a)
{
String safe = “it is safe!”
const char * unsafe = new char[100];
unsafe = “it is unsafe!”
//char * unsafe = new char[100]{ "it is unsafe!" };
If(a == 0)
throw a;
cout<<”a不等于0”<<endl;
delete []unsafe
return a+1;
}
对于string类对象safe,会在throw后,调用string的析构函数,因此内存没有泄露。
对于用new申请的动态内存,在throw后,程序中止而未执行delete,因此造成了内存泄漏。
int exception_safe(int a)
{
String safe = “it is safe!”
const char * unsafe = new char[100];
unsafe = “it is unsafe!”
//char * unsafe = new char[100]{ "it is unsafe!" };
If(a == 0)
throw a;
catch(int i)
{
delete []unsafe
throw;//将捕捉的int异常再扔出去
}
cout<<”a不等于0”<<endl;
return a+1;
}
这类似于双重校验的模式,通过一次catch来执行后面必须要执行的代码;然后再throw,将之前catch的异常扔出去。注:这里的throw是扔出之前catch的异常。