异常的概念
异常是程序在 运行期间 产生的问题,程序可以正常通过编译,没有语法问题,只是由于在运行中触发了一些C++预定义的逻辑问题,从而产生了异常现象。异常是需要正确的处理的,如果没有得到正确处理,则程序运行会终止
处理异常的两种方式:
●抛出异常 throw
●捕获异常 try-catch
抛出异常 throw
C++底层和程序员都可以抛出异常,例如在下面的代码中,C++底层会抛出一个异常对象,这个异常对象会优先在当前代码寻找处理的措施,当找不到时,会向上转移,直到找到处理的措施,一直向上转移到主函数还找不到处理措施就会运行终止
#include <iostream>
using namespace std;
void test1()
{
string s = "23";
cout << s.at(34) << endl; // 抛出异常对象
}
void test2()
{
test1();
}
int main()
{
test2();
cout << "主函数执行结束" << endl;
return 0;
}
上面的代码运行到第8行,C++底层检测到下标范围越界,会抛出一个异常对象,这个对象首先在第8行寻找处理措施的相关代码,找不到就去上一级第13行寻找,找不到再去第19行寻找,直到主函数还是没有处理这个异常,于是程序运行终止,并输出异常对象携带的相关信息。
除了C++底层固定的一些错误的逻辑操作会抛出异常外,程序员也可以自己制定抛出的时机和抛出的异常对象。因为C++的异常体系不是特别健全,因此如果要在项目中使用异常处理,程序员自身规定一些逻辑错误的情况,并抛出对应的异常对象就显得相对重要了
#include <iostream>
using namespace std;
double division(double a,double b)
{
// 手动抛出异常对象
if(b == 0)
throw "不能除以0!!!!";
return a/b;
}
int main()
{
double d = division(3,0);
cout << d << endl;
cout << "主函数执行结束" << endl;
return 0;
}
异常捕获 try-catch
把可能出现异常的代码放置到try代码块中,在catch代码块中编写对应的处理逻辑。try块中尽量少放代码,非异常代码在try中虽然也可以得到正常执行的,但是效率偏低。
try块必须配合catch块进行异常类型匹配。需要注意的是,catch块如果匹配的类型不一致,整个异常捕获都会失效
#include <iostream>
using namespace std;
double division(double a,double b)
{
// 手动抛出异常对象
if(b == 0)
throw "不能除以0!!!!";
return a/b;
}
int main()
{
try
{
double d = division(3,0);
cout << d << endl;
}
catch(const char* e)
{
cout << e << endl;
cout << 1 << endl; // 弥补措施
}
cout << "主函数执行结束" << endl;
return 0;
}
自定义异常
上面的代码中,手动抛出的异常类型是char*,这种类型不是推荐的异常类型,一个标准的类型应该从继承结构上属于标准异常家族。
以下是C++标准异常库的相关类型:
使用上面的异常类型需要引入头文件 #include stdexcept(尖括号在scdn文档中会隐藏掉括号中的内容)
标准异常库的类型相对较少,无法覆盖很多开发的情况,程序员可以自定义一个异常类继承标准异常库中异常类型
#include <iostream>
#include <stdexcept>
using namespace std;
class ZeroException:public exception
{
public:
// 覆盖what函数
const char* what() const throw()
{
return "不能除以0!!!!";
}
};
double division(double a,double b)
{
// 手动抛出异常对象
if(b == 0)
throw ZeroException();
return a/b;
}
int main()
{
try
{
double d = division(3,0);
cout << d << endl;
}catch(const ZeroException& e)
{
cout << e.what() << endl; // 打印异常信息
cout << 1 << endl; // 弥补措施
}
cout << "主函数执行结束" << endl;
return 0;
}
之所以要自定义异常类型,并继承自标准异常,是因为异常的捕获不光可以匹配精准的类型,也可以匹配粗略的类型(基类)。
异常捕获也支持多重捕获,类似于switch-case,case语句可以若干个,catch语句也可以有若干个。如果使用多重捕获,一定要先捕获派生类异常类型,再捕获基类异常类型,可以认为基类异常类型是一种保底的措施
#include <iostream>
#include <stdexcept>
using namespace std;
class ZeroException:public exception
{
public:
// 覆盖what函数
const char* what() const throw()
{
return "不能除以0!!!!";
}
};
double division(double a,double b)
{
// 手动抛出异常对象
if(b == 0)
throw ZeroException();
return a/b;
}
int main()
{
try
{
double d = division(3,0);
cout << d << endl;
}catch(ZeroException e)
{
cout << e.what() << endl; // 打印异常信息
cout << 2 << endl; // 弥补措施
}catch(exception e)
{
cout << e.what() << endl; // 打印异常信息
cout << 1 << endl; // 弥补措施
}
cout << "主函数执行结束" << endl;
return 0;
}
try-catch块的执行逻辑是:
另外,可以使用…匹配任意异常类型,包括非标准异常库类型