C++进阶(语法篇)第8章—异常

本文深入探讨C++中的异常处理机制,包括程序异常的概念、使用abort与exit函数的后果、C++异常机制的实现方式,如try-catch语句的使用、栈解退过程、类对象作为异常的处理,以及类继承在异常处理中的应用。同时,文章讲解了如何处理意料之外的异常,并介绍了异常处理中的注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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的异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值