异常

本文详细介绍了异常处理的基本概念,包括异常的抛出与捕获、栈展开过程、捕获匹配规则及异常重新抛出等内容,并探讨了异常处理的优缺点。

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

一、传统处理错误的办法

  • 直接终止程序(如段错误等)
  • 返回错误码
  • 返回一个合法的值,让程序继续进行运行
  • 调用一个预先设置好产生错误时要调用的函数--回调函数

二、异常处理错误

什么是异常?当一个函数发现自己无法处理的错误时,抛出异常,让函数调用者直接或间接的处理这个问题。

void fun()
{
	try
	{
		char*a = new char[0x7fffffff];
	}
	catch (exception e)
	{
		cout << e.what() << endl;
	}

}

也可以让它的调用者捕获这个异常,并进行处理:

void fun()
{
	char*a = new char[0x7fffffff];
}
int main()
{
	try
	{
		fun(); 
	}
	catch (exception& e)//在这个模块内可以对异常进行处理
	{
		cout << e.what() << endl;
	}
	system("pause");
	return 0;
}

三、异常的抛出和捕获

  • 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个处理代码。
void fun()
{
	//char*a = new char[0x7fffffff];
	int errid = 11;
	throw errid;
}
int main()
{
	try
	{
		fun(); 
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	catch (int& e)
	{
		cout << "捕获一个整型异常:"<< e << endl;
	}
	system("pause");
	return 0;
}

可以看到异常类的catch并没有捕获整型的异常,而是整型的异常才捕获到了这个异常。

  •  被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
void fun()
{
	//char*a = new char[0x7fffffff];
	int errid = 11;
	throw errid;
}
void fun1()
{
	try
	{
		fun();
	}
	catch (int& e)
	{
		cout << "优先调用fun函数的函数" << endl;
	}
}
int main()
{
	try
	{
		fun1(); 
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	catch (int& e)
	{
		cout << "间接调用fun函数 捕获一个整型异常:"<< e << endl;
	}
	system("pause");
	return 0;
}

从结果中可以是main函数和fun1函数都直接或间接地调用了fun函数,且都尝试catch了fun函数抛出的异常,然而是在调用链中离抛出异常近的函数捕获了这个异常:


  • 抛出异常后会释放局部存储对象,所以被抛出的对象也就还给系统了,throw表达式会初始化一个抛出特殊的异常对象副本(匿名对象),异常对象由编译管理,异常对象在传给对应的catch处理之后撤销。

四、异常的栈展开

在抛出异常之后,会暂停函数逻辑的执行,在当前调用栈查找是否有catch异常的语句,如果有,在检查catch的类型是否匹配,如果有,则进行处理;如果没有,就在调用它的函数中进行查找(在上一层栈桢中进行查找),...一直进行下去,直到找到匹配的catch语句或者到达main函数栈桢还是没有找到就终止程序。这样沿着调用链进行查找叫做栈展开

例如:


五、异常捕获的匹配规则

异常对象的类型与catch说明符的类型必须完全匹配。
只有以下几种情况例外
1. 允许从非const对象到const的转换。
2.
允许从派生类型到基类类型的转换。

3. 将数组转换为指向数组类型的指针,将函数转换为指向函数类型的指针。

六、异常的重新抛出

有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理。

例如:在下面的例子中若直接对异常进行处理,可能会造成内存泄漏

void fun()
{
	int errid = 11;
	throw errid;
}
void fun1()
{
	char*a = new char[7];
	try
	{
		fun();
	}
	catch (...)   //其它异常
	{
		cout << "未知异常" << endl;
		throw;	//异常的重新抛出
	}
	delete[] a;
}

七、异常与构造函数与析构函数

1. 构造函数完成对象的构造和初始化,需要保证不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化。
2. 析构函数主要完成资源的清理,需要保证不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏、句柄未关闭等)


八、异常的优缺点:

相比于错误码的方式,异常有以下优点:

1)比传统错误码的方式更能清晰地表示程序错误;

2)很多第三方库都在使用异常;

3)很多测试框架也在使用异常;

异常也有一些缺点:

1)使用异常会有异常安全的问题;

2)可能会导致执行流的混乱。









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值