C++异常机制知识点
在这里总结一下,C++中的异常机制,以及如何使用异常的知识点
C++中处理异常的过程是这样的:在执行程序发生异常,可以不在本函数中处理,而是抛出一个错误信息,把它传递给上一级的函数来解决,上一级解决不了,再传给其上一级,由其上一级处理。如此逐级上传,直到最高一级还无法处理的话,运行系统会自动调用系统函数terminate,由它调用abort终止程序。这样的异常处理方法使得异常引发和处理机制分离,而不在同一个函数中处理。这使得底层函数只需要解决实际的任务,而不必过多考虑对异常的处理,而把异常处理的任务交给上一层函数去处理。
总结C++异常机制的用法概念
首先异常机制中最重要的三个关键字就是:throw try catch。
Throw抛出异常,try 包含异常模块,catch 捕捉抛出的异常,三者各有各的分工,集成在一起就构成了异常的基本机制。
我们为什么要检测异常,抛出异常,?
程序做错误检查是必要的,通常我们可以通过返回值告诉客户有了错误,不过异常提供了更加方便的手段和丰富的信息。举个例子,一个游戏软件,需要判断你在打游戏中选择的关口(类似于地下城勇士中勇士级,王者级等),其中每一个关口都对应这一个ID(这是必须的),当你等级不够时,不能选择高等级的,此时就要抛出一个异常,提示你等级不够,不可以进入,例子可能有些不恰当,但足以说明使用异常处理的重要性。废话不说了,下面详细介绍下如何使用异常处理。
如果在try语句块的程序段中(包括在其中调用的函数)发现了异常,且抛弃了该异常,则这个异常就可以被try语句块后的某个catch语句所捕获并处理,捕获和处理的条件是被抛弃的异常的类型与catch语句的异常类型相匹配。由于C++使用数据类型来区分不同的异常,因此在判断异常时,throw语句中的表达式的值就没有实际意义,而表达式的类型就特别重要
以异常语句如下:
try
{
可能会出异常的函数或者语句,判断是否出现异常,抛出异常
}
Catch(异常类型 [参数名字] )
{
捕捉异常
}
其中catch语句可以有多个,以捕捉不同的异常。
(1)异常类型我们可以使用自己定义的,我们写一个程序,除法程序,除数为零,自定义抛出异常类型。
#include<iostream> #include<stdexcept> using namespace std; class divide_zero //自定义异常类型 {}; int divide(int c, int d) //定义函数,异常类型说明 此处不写,则说明是此函数有可能抛出各种异常 { if(d==0) throw divide_zero();//抛出异常类型为 除零异常类型 cout<<"Correct the result is"<<c/d<<endl; return 0; } int main() { int a,b;
//使用自定义异常类型 cout<<"Please enter the two different integer "<<endl; cin>>a>>b; try { divide(a,b); //可能发生异常的函数 } catch(divide_zero) //捕捉到除数为零的异常 { cout<<"Division by zero"<<endl; } return 0; }运行结果是:
(2)异常类型可以是我们定义的类,我们也可以定义异常类型是基本数据类型,为此,我们修改上述代码
#include<iostream> #include<stdexcept> using namespace std; int divide(int x,int y) { if(y==0) throw y; cout<<"Correct the result is"<<x/y<<endl; return 0; } int main() { //自定义类型之使用基本数据类型 try { divide(1,0); } catch(<span style="color:#ff0000;">int</span>) //<span style="color:#ff0000;">因为除数是整形所以抛出异常时类型是整形才能捕捉(0也是整形)</span> { cerr<<"error of dividing zero./n"; exit(1); //异常退出程序 } }运行结果是:
此时我们也可以用C++的标准异常,为此我们修改上面代码,我们先介绍下C++的标准异常类,使用C++提供给我们的标准异常时,我们需要包含头文件#include<stdexcept>
以下是我查找的C++标准异常类型,引用一个大神的文章
namespace std
{
//exception派生
class logic_error; //逻辑错误,在程序运行前可以检测出来
//logic_error派生
class domain_error; //违反了前置条件
class invalid_argument; //指出函数的一个无效参数
class length_error; //指出有一个超过类型size_t的最大可表现值长度的对象的企图
class out_of_range; //参数越界
class bad_cast; //在运行时类型识别中有一个无效的dynamic_cast表达式
class bad_typeid; //报告在表达试typeid(*p)中有一个空指针p
//exception派生
class runtime_error; //运行时错误,仅在程序运行中检测到
//runtime_error派生
class range_error; //违反后置条件
class overflow_error; //报告一个算术溢出
class bad_alloc; //存储分配错误
}
其中的一个重要函数为what(),它返回一个表示异常的字符串指针。
标准库异常类定义在以下四个头文件中
1、exception头文件:定义了最常见的标准异常类,其类名为exception。只通知异常的产生,但不会提供更多的信息
2、stdexcept头文件定义了以下几种常见异常类
函数 功能或作用
exception 最常见的问题
runtime_error 运行时错误:仅在运行时才能检测到的问题
range_error 运行时错误:生成的结果超出了有意义的值域范围
overflow_error 运行时错误:计算上溢
underflow_error 运行时错误:计算下溢
logic_error 逻辑错误:可在运行前检测到的问题
domain_error 逻辑错误:参数的结果值不存在
invalid_argument 逻辑错误:不合适的参数
length_error 逻辑错误:试图生成一个超出该类型最大长度的对象
out_of_range 逻辑错误:使用一个超出有效范围的值
了解完后,我们开始编写程序,在此我们只是简单地使用下标准异常类。
#include<iostream> #include<stdexcept> using namespace std; int main() { //抛出一个小小的异常 使用C++标准异常类 int a,b; cout<<"Please enter the two different integer "<<endl; while(cin>>a>>b) { try { if(b==0) throw runtime_error("两者不可以相等"); } catch(runtime_error err) { cout<<err.what()<<endl; } cout<<"请继续输入"<<endl; } return 0; }运行结果是:
异常的接口声明
为了加强程序的可读性,使函数的用户能够方便地知道所使用的函数会抛出哪些异常,可以在函数的声明中列出这个函数可能抛出的所有异常类型,例如:
void fun() throw( A,B,C,D);
这表明函数fun()可能并且只可能抛出类型(A,B,C,D)及其子类型的异常。如果在函数的声明中没有包括异常的接口声明,则此函数可以抛出任何类型的异常,例如:
void fun();
一个不会抛出任何类型异常的函数可以进行如下形式的声明:
void fun() thow();
此时一定要注意:编译器不会对异常说明进行检测,异常说明更多的是写给函数的用户看。
而在一开始函数异常说明的类型与实际抛出的异常类型不匹配的情况中,同样由于“编译器不会对异常说明进行检测”的原因,所以编译通过,异常机制运行正常。比如说,你的声明的类型是整形,结果还是让除数为零的异常接收到了,有人感觉很奇怪,其原因就在于此。