C++_异常

       

目录

1、异常的关键字

2、异常的写法

3、异常的使用规则

3.1 规则1

3.2 规则2

3.3 规则3 

3.4 规则4

3.5 规则5 

4、异常的重新抛出 

5、异常的规范 

5.1 C++98的异常规范

5.2 C++11的异常规范

6、C++标准库的异常体系

7、异常的优缺点

结语 


前言:

        C++的异常是一种处理程序bug的方式,如今面向对象的语言基本都是用异常处理错误的,因为他可以帮助程序员快速定位错误的根源。当一个函数或者是一段代码发现了自己处理不了的错误就可以将该错误抛出,又称异常抛出,由外部的调用者或间接调用者捕获该错误,又称异常捕获

1、异常的关键字

         若要使用异常则需要用到三个关键字:try、throw、catch。他们的具体用法如下:

        try:try有属于自己的作用域,在他的作用域中存放可能会抛出异常的代码或者函数。

        throw:throw有着“抛出”的动作,throw后面跟一段内容,当程序出现错误时,throw会抛出其后面的内容(即抛出异常),作为错误的描述,通常throw后面跟一段字符串作为错误描述。

        catch:catch是捕获的意思,并且他只能捕获try里面代码的异常。当throw抛出了异常,那么就需要catch捕获该异常,不过要先捕获该异常,catch的捕获类型必须与throw抛出内容的类型一致才可以捕获。(具体看下文举例)

2、异常的写法

        异常的代码格式如下:

try
{
  // 可能会抛异常的代码
}catch( type e1 )//抛出的异常用形参e1捕获
{
  // 通常对e1的内容进行打印
}catch( type e2 )//可以有不同的捕获类型
{
  // 如上
}catch( type en )
{
  // 如上
}

        举例说明:

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

int main()
{
	try
	{
		int a;
		cin >> a;
		if(a < 0)//如果输入的a小于0说明发生错误
		throw "a为负数发生错误";//抛异常:a为负数发生错误(类型是char*)
	}
	catch (const char* str)//因为抛出的因此类型是字符串,因此可以用char*接收
	{
		cout << str << endl;//打印异常的错误
	}
	return 0;
}

         运行结果:

3、异常的使用规则

3.1 规则1

        抛出异常和捕获异常的类型必须匹配,否则是捕获不了该异常的,若不匹配先会去找其他的catch能否类型匹配,若没找到则说明抛出的异常没有对应的捕获类型,则会报错。

        示例如下图:

3.2 规则2

        抛出的异常会被离该异常最近位置的catch捕获(前提是该catch与异常类型匹配),示例代码如下:

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

void _func()
{
	int a = -2;
	if (a < 0)//如果输入的a小于0说明发生错误
		throw "a为负数发生错误";//抛异常:a为负数发生错误
}

void func()
{
	try
	{
		_func();//复用
	}
	catch (const char* str)//func函数的捕获
	{
		cout << "void func():" << str << endl;//打印异常的错误
	}
}

int main()
{
	try
	{
		func();
	}
	catch (const char* str)//main函数的捕获
	{
		cout << "main()" << str << endl;//打印异常的错误
	}
	
	return 0;
}

         运行结果:

        从结果可以看到,该异常被离他最近的func函数所捕获,假如此时的func函数的捕获类型不能与异常类型相匹配,才会让main函数中的catch去捕获。 

3.3 规则3 

        若catch捕获到了该try中的异常,则会从捕获该异常的catch之后开始执行剩下的代码(跳过其他的catch) ,示例代码如下:

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

void _func()
{
	int a = -2;
	if (a < 0)//如果输入的a小于0说明发生错误
		throw "a为负数发生错误";//抛异常:a为负数发生错误
}

void func()
{
	_func();//复用
}

int main()
{
	try
	{
		func();
	}
	catch (const char* str)
	{
		cout << str << endl;//打印异常的错误
	}
	catch (const int* str)//若异常已经被捕获,则会跳过该catch
	{
		cout << str << endl;
	}
	cout << "继续执行catch的下面代码" << endl;
	
	return 0;
}

        运行结果:

3.4 规则4

        当一个函数栈帧中抛出了异常,则编译器先会在该栈帧中查看是否有合适的catch捕获,若没有则结束该栈帧,回到调用该函数的上一层栈帧中查看是否有合适的catch,若没有也结束当前栈帧,直到回到main函数中若还没有合适的catch则直接报错(参考规则1),该过程又叫栈回收

        具体示意图如下:

         所以,从上面可以发现,如果没有合适的catch,则程序会被直接终止且报错,为了避免这种极端情况,一般会用catch(...)来接收任意类型的异常。

         万能捕获catch(...)示例代码如下:

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

void _func()
{
	int a = -2;
	if (a < 0)//如果输入的a小于0说明发生错误
		throw "a为负数发生错误";//抛异常:a为负数发生错误
}

void func()
{
	_func();//复用
}

int main()
{
	try
	{
		func();
	}
	catch (const int* str)
	{
		cout << str << endl;//打印异常的错误
	}
	catch (...)//万能捕获
	{
		cout << "其他类型错误" << endl;//打印异常的错误
	}
	
	return 0;
}

        运行结果:

         从结果可以看到,只要有了万能类型捕获,则无论是什么样类型的异常都不会因为没能捕获而导致程序直接被终止。

3.5 规则5 

         抛出的异常也可以是一个对象,这时候抛出的并不是对象本身,因为catch在别的作用域,所以抛出的是该对象的一个拷贝对象,并且该拷贝对象的生命周期会在catch之后销毁,所以catch的形参可以用引用接收,该形参是拷贝对象的别名而不是抛出的局部对象的别名。示例代码如下:

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

void _func()
{
	int a = -2;
	if (a < 0)//如果输入的a小于0说明发生错误
		throw string("a为负数发生错误");//抛出的是string类型
}

void func()
{
	_func();//复用
}

int main()
{
	try
	{
		func();
	}
	//捕获的类型是string,并且可以用引用,因为返回的是拷贝对象
	catch (const string& str)
	{
		cout << str << endl;//打印异常的错误
	}
	catch (...)//万能捕获
	{
		cout << "其他类型错误" << endl;//打印异常的错误
	}

	return 0;
}

        运行结果:

4、异常的重新抛出 

        异常的重新抛出是为了解决空间资源和捕获异常同时出现的情况,上文提到捕获异常时,如果在别的函数栈帧中才捕获该异常,则编译器会结束之前的栈帧,那么如果之前的栈帧里涉及到空间资源的问题,则会引发一个问题:其他栈帧里的空间还没来得及释放就因为要捕获异常而结束了该栈帧,会导致内存泄漏的问题。

        示例代码如下:

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

void _func()
{
	int a = -2;
	if (a < 0)//如果输入的a小于0说明发生错误
		throw string("a为负数发生错误");//抛出的是string类型
}

void func()
{
	int* arr = new int;//涉及到空间资源的管理
	try
	{
		_func();
	}
	catch (...)//函数func中必须捕获该异常才能释放申请的空间,否则直接跳到main函数了
	{
		cout << "delete arr" << endl;
		delete arr;
		throw;//重新抛出的写法
	}
	cout << "delete arr" << endl;
	delete arr;
}

int main()
{
	try
	{
		func();
	}
	//捕获的类型是string,并且可以用引用,因为返回的是拷贝对象
	catch (const string& str)
	{
		cout << str << endl;//打印异常的错误
	}
	catch (...)//万能捕获
	{
		cout << "其他类型错误" << endl;//打印异常的错误
	}

	return 0;
}

        运行结果:

5、异常的规范 

        异常的规范可以让函数的使用者清晰的看到该函数会抛出什么类型的异常或会不会抛出异常。

5.1 C++98的异常规范

        throw()是C++98的异常规范关键字,他通常写在函数接口的后面,具体用法如下:

// 这里表示这个函数会抛出int,char,char*中的某种类型的异常
void func() throw(int,char,char*);

// 这里表示这个函数不会抛出异常
void func() throw();

//在异常规范中,没有声明throw()则表示该函数可能抛出任意类型的异常
void func()

5.2 C++11的异常规范

        C++11对应异常规范新增了一个关键字:noexcept,该关键字同样写在函数的接口后面,表示该函数不会抛任何的异常,用法如下:

//表示func不会抛异常
func() noexcept;

        并且noexcept严格执行规范,若一个函数加了noexcept结果该函数会抛出异常,那么运行后就直接报错,并且复用该函数的函数也会判断为会抛出异常的函数。

        测试代码如下:

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

void _func()
{
	int a = -2;
	if (a < 0)//如果输入的a小于0说明发生错误
		throw string("a为负数发生错误");//抛出的是string类型
}

void func()noexcept//给func函数加上noexcept,事实上该函数会抛异常,运行后报错
{
	int* arr = new int;//涉及到空间资源的管理
	try
	{
		_func();
	}
	catch (...)//函数func中必须捕获该异常才能释放申请的空间,否则直接跳到main函数了
	{
		cout << "delete arr" << endl;
		delete arr;
		throw;//重新抛出的写法
	}
	cout << "delete arr" << endl;
	delete arr;
}

int main()
{
	try
	{
		func();
	}
	//捕获的类型是string,并且可以用引用,因为返回的是拷贝对象
	catch (const string& str)
	{
		cout << str << endl;//打印异常的错误
	}
	catch (...)//万能捕获
	{
		cout << "其他类型错误" << endl;//打印异常的错误
	}

	return 0;
}

        运行结果:

6、C++标准库的异常体系

        C++标准库中也提供了一部分异常信息可供用户捕获,以便让程序员使用时可以更好的定位错误根源。这些异常类型是以继承和多态的结构存在的,他们都有一个共同的父类exception,所以程序员只需要用该父类作为捕获的类型,然后根据多态的条件(父类指针或引用会调用子类的虚函数)就可以打印出来正确的错误信息了。

        关系图:

        具体异常信息如下图:

         标准库里的异常信息使用方式如下:

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
#include<vector>
using namespace std;

int main()
{
	try {
		vector<int> v1;
		// 这里如果系统内存不够也会抛异常
		v1.reserve(1000000000);
	}
	catch (const exception& e) // 这里捕获父类对象就可以
	{
		cout << e.what() << endl;
	}
	
	return 0;
}

        运行结果:

7、异常的优缺点

         异常的优点:

        1、异常可以展现出清晰的错误信息,以便帮助程序员快速的定位错误位置。

        2、异常可以直接抛出给最外层,这样一来可以在统一的地方进行处理异常,即使复用的函数栈帧很深时也无需每层栈帧都检查一遍是否有异常。

        3、一些常用的库也存在异常的概念,如boost、gtest、gmock。

        4、有些特殊的函数不能有返回值,比如构造函数,因此不能用错误码一层层返回的方式来定位构造函数的错误,所以构造函数出了bug只能用异常发现问题。

        异常的缺点:

        1、 捕获异常时若涉及空间资源管理则稍不谨慎就会发生内存泄漏。

        2、因为抛出的异常会直接跳到main函数中,所以有些场景下调试起来不方便。

        3、异常规范并不是强制行为,因此可能会出现有些代码没加noexcept,实际上却没有抛异常的可能。

        总结:总而言之异常还是利大于弊的。

结语 

        以上就是关于C++异常的讲解,异常在程序中发挥着至关重要的作用,有了异常就能快速定位bug,帮助程序员节省了改bug的时间,最后希望本文可以给你带来更多的收获,如果本文对你起到了帮助,希望可以动动小指头帮忙点赞👍+关注😎+收藏👌!如果有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!  

相关推荐

  1. <span style='color:red;'>C</span>++<span style='color:red;'>异常</span>

    C++异常

    2024-03-13 12:58:04      49 阅读
  2. <span style='color:red;'>C</span>++<span style='color:red;'>异常</span>

    C++异常

    2024-03-13 12:58:04      27 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-13 12:58:04       91 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-13 12:58:04       97 阅读
  3. 在Django里面运行非项目文件

    2024-03-13 12:58:04       78 阅读
  4. Python语言-面向对象

    2024-03-13 12:58:04       88 阅读

热门阅读

  1. 力扣- 704. 二分查找

    2024-03-13 12:58:04       35 阅读
  2. vue的生命周期详解

    2024-03-13 12:58:04       40 阅读
  3. 【计算机网络】HTTP协议

    2024-03-13 12:58:04       44 阅读
  4. TCP通信程序

    2024-03-13 12:58:04       48 阅读
  5. 分布式微服务 - 2.服务注册 - 5.Nacos

    2024-03-13 12:58:04       41 阅读
  6. 使用fegin遇到的俩个问题

    2024-03-13 12:58:04       37 阅读