C++——异常

C++——异常_第1张图片


异常

C语言中的异常

在C语言里,我们传统的处理错误的方式只有两种:

  1. 手动条件判断并终止程序,比如assert断言;
  2. 返回错误码, 然后对照错误码找错误。

但是,无论是哪种方法,最后都会面临一种问题:

C++——异常_第2张图片

C语言中的错误码都只是告诉你大致错误的类型,却不会告诉你到底哪里出错了,而且也不会告诉特别详细的错误,所以让修bug的人往往直接开摆

C++——异常_第3张图片

但是,C++中便有了新的处理错误的方式:抛异常

C++中的异常

在C++里,如果某一个函数有自身无法处理的错误,便可以进行抛异常。抛出异常后,该函数立即终止,返回到上一层函数中;如果上一个函数也没办法处理这个异常,则接着抛出到更外一层,一直到能接收这个异常为止。

C++——异常_第4张图片


C++处理异常的方法

异常的抛出和捕获

在C++中,有三个处理异常的关键字:

  • throw:当问题出现时,便会通过throw抛出异常,结束该函数并将异常抛到上一层函数
  • catch:捕获异常,当异常被抛出时,catch可以接收到异常,并判断是否可以被捕获
  • try:try是catch的先制条件,catch只可以捕获try中的异常

直接看概念非常抽象,不妨看一个例子:

void push_back(char* a,char ins, int num, int size)
{
	//num代表数组元素个数,size代表数组大小
	//如果数组元素个数等于数组大小,则表示数组已满,抛出异常
	if (num == size)
	{
		throw "数组已满";
	}

	//如果没有抛出异常,则程序正常运行
	//如果抛出了异常,则throw相当于return,往后不会继续运行
	a[num] = ins;
}

void test()
{
	char a[1];

	
	try {
		push_back(a, 'c', 0, 1);
	}
	//catch只会捕获try中的异常,然后将异常捕获到s中
	//这里函数没有抛出异常,catch捕获了个寂寞,所以不走catch语句
	catch (const char* s)
	{
		cout << s << endl;
	}

	try {
		push_back(a, 'c', 1, 1);
	}
	//这里函数抛出了异常,catch捕获到异常:“数组已满”,并将异常存储到s中
	//于是程序输出“数组已满”,然后该test函数继续运行
	catch (const char* s)
	{
		cout << s << endl;
	}

}

int main()
{
	test();
}

但是可能会出现一种情况:catch中捕获的变量和抛出异常的变量类型不一样,那又是个啥效果?

void push_back(char* a, char ins, int num, int size)
{
	if (num == size)
	{
		throw "数组已满";
	}

	a[num] = ins;
}

void test()
{
	char a[1] = { 'c' };

	try {
		push_back(a, 'c', 1, 1);
	}
	//抛出和捕获的不一样
	catch (int i)
	{
		cout << i << endl;
	}
}

int main()
{
	test();
}

C++——异常_第5张图片

但是,是不是只要捕获的类型和抛出的类型不一样,程序就会报错呢?也不是

void push_back(char* a, char ins, int num, int size)
{
	if (num == size)
	{
		throw "数组已满";
	}

	a[num] = ins;
}

void test()
{
	char a[1] = { 'c' };

	try {
		push_back(a, 'c', 1, 1);
	}
	//抛出和捕获的不一样
	catch (int* i)
	{
		cout << i << endl;
	}

	//当该函数无法处理这个异常(即捕获的类型和抛出的类型不一样)
	//则函数也会立即结束,抛出异常
	cout << "test" << endl;
}

int main()
{
	//在main函数再捕获一次
	try {
		test();
	}
	//一直到最后一个函数也无法处理这个错误,程序才会终止并报错
	catch (const char* s)
	{
		cout << s << endl;
	}
}

当本函数无法处理的时候,则会抛出异常到再外一层,然后本函数立即终止, 往复循环,一直到能完美捕获到这个异常为止

C++——异常_第6张图片

总结以上:假设我们有函数1,2,3,栈帧调用顺序为1->2->3,运行到了函数3

  1. 如果没有达到异常条件,没有抛出异常,则函数和程序都正常运行
  2. 如果3达到异常条件,抛出了异常,则该throw相当于return,函数3立即销毁,并将异常信息返回到函数2try对应的catch中。
  3. 如果函数2的catch类型可以匹配,则运行catch中的内容,然后程序再继续正常运行。
  4. 如果函数2的catch类型无法匹配,则函数2立即销毁,将该异常信息继续向函数1抛出。
  5. 如果函数1的catch类型可以匹配,则函数1运行catch中的内容,然后函数1继续正常运行
  6. 如果函数1的catch类型无法匹配,则函数1立即销毁。如果函数1是主函数,则代表该异常无法被捕获到,程序报错

同时,为了避免一个try可能收到多种异常类型,catch也可以根据类型写出不同的情况:

void test()
{
	try
	{
		//可能会返回多种异常类型
		func();
	}
	catch (int i)
	{
		//...
	}
	catch (string s)
	{
		//...
	}
	catch (char c)
	{
		//...
	}
	//表示其他的所有情况
	catch (...)
	{
		//...
	}
}

 异常的再抛出

catch在捕获到异常时,有时该函数并不会处理异常,而是在将该异常信息进行修正后,再去将异常抛到下一层,起到传球的作用

void throw_error()
{
	string s="error";
	throw s;
}

void func1()
{
	try {
		throw_error();
	}
	//修正异常信息,表示异常来自于函数func1
	//一定要用引用,抛出的是最初的异常信息
	catch (string& s)
	{
		s += "_from_func1";

		//再抛出
		throw;
	}
}

void func2()
{
	try {
		throw_error();
	}
	//修正异常信息,表示异常来自于函数func2
	catch (string& s)
	{
		s += "_from_func2";

		//再抛出
		throw;
	}
}

int main()
{
	try {
		func1();
		func2();
	}
	catch(const string& s)
	{
		cout << s << endl;
	}
}

异常的安全

异常的一大缺点便是——会产生内存泄漏的问题

void func1()
{
	int* a = new int[10];

	if (1)
	{
		throw "error";
	}

	//如果产生异常,则a的空间不会被释放
	delete[]a;
}

 如果程序中产生了异常,则后面的代码段不会继续运行。一般情况下,不会出什么问题,但是如果后面进行了delete或者free,那就会出大问题——内存泄漏了。

一般来说,我们只能通过RAII来解决这一问题。但是RAII是什么?别急,在智能指针中马上就会有讲解。


智能指针

C++——智能指针和RAII-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_74260823/article/details/135182056?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22135182056%22%2C%22source%22%3A%22qq_74260823%22%7D


 C++——异常_第7张图片

你可能感兴趣的:(C++,c++,开发语言)