目录
C ++ 异常处理:https://www.runoob.com/cplusplus/cpp-exceptions-handling.html
0基础概念
C ++异常是指在程序运行时发生的特殊情况,例如:除0溢出,数组下标越界,所要读取的文件不存在,内存不足等问题
C ++异常处理涉及到三个关键字:try,catch,throw。
- throw:当问题出现时,程序会引发一个异常。这是通过使用throw关键字来完成的。
- catch:在您想要处理问题的地方,通过异常处理程序捕获异常。
- try: 尝试块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个catch块。
如果有一个块抛出一个异常,捕获异常的方法会使用try和catch关键字。
try块中放置可能引发异常的代码,try块中的代码被称为保护代码。使用try / catch语句的语法如下所示:
头文件:
#include<exception>
基本语法:
try
{
// 保护代码
}catch( ExceptionName e1 )
{
// catch 块
}catch( ExceptionName e2 )
{
// catch 块
}catch( ExceptionName eN )
{
// catch 块
}
如果 try 块在不同的情境下会抛出不同的异常,这个时候可以尝试罗列多个 catch 语句,用于捕获不同类型的异常。
关于构造和析构是否能抛出异常:
1.不建议在构造函数中抛出异常;
2.构造函数抛出异常时,析构函数将不会被执行,需要手动的去释放内存
1.析构函数不应该抛出异常;
2.当析构函数中会有一些可能发生异常时,那么就必须要把这种可能发生的异常完全封装在析构函数内部,决不能让它抛出函数之外;
3. 析构函数异常相对要复杂一些,存在一种冲突状态,程序将直接崩溃:异常的被称为“栈展开(stack unwinding)”【备注】的过程中时,从析构函数抛出异常,C++运行时系统会处于无法决断的境遇,因此C++语言担保,当处于这一点时,会调用 terminate()来杀死进程。因此,当处理另一个异常的过程中时,不要从析构函数抛出异常, 抛出异常时,其子对象将被逆序析构
(答案来源@SunburstRun)
1异常捕获语法
C中 处理异常/保护程序 的写法:
1.1基本写法
普通情况
//throw过程
void Protect_function()
{
throw Data;
}
//try catch过程
void Except_function()
{
try
{
Protect_function();//保护程序
}
catch(Data_type)
{
//输出:qDebug()或者cout<<....
//如果不想处理,可以写:throw;
}
}
其中Data的类型是Data_type, 二者必须保持一致。
Data为自定义类型(类或者结构体)
//throw过程
void Protect_function()
{
throw Classname();//匿名对象
}
//try catch过程
void Except_function()
{
try
{
Protect_function();//保护程序
}
catch(Classname & e)
{
e.MemberFunction;//调用该类中专门的打印错误信息的成员函数
//输出:qDebug()或者cout<<....
//如果不想处理,可以写:throw;
}
}
接受一切throw信息
//throw过程
void Protect_function()
{
throw whatever;//匿名对象
}
//try catch过程
void Except_function()
{
try
{
Protect_function();//保护程序
}
catch(...)
{
//输出:qDebug()或者cout<<....
//如果不想处理,可以写:throw;
}
}
catch(...)就是接受一切throw信号
1.2限定throw类型写法
有时候对throw的类型有限制,什么都不加,就是没有限制,你可以throw任意类型
加上限制的情况如下:
//throw过程
void Protect_function() throw(DataType1,....,DataTypen)
{
throw Data;//form DataType1,....,DataTypen
}
//try catch过程
void Except_function()
{
try
{
Protect_function();//保护程序
}
catch(DataTypex)//x = 1,....,n;
{
//.........
}
}
thorw的内容,必须是规定的内容
当然,也可以选择不throw任何的内容
//throw过程
void Protect_function() throw( )
{
//..........
}
//try catch过程
void Except_function()
{
try
{
Protect_function();//保护程序
}
catch( )
{
//.........
}
}
1.3调用C++标准异常写法
try
{
//protect
}
catch(系统异常类名 & e)
{
cout<<endl<<e.what()<<endl;
}
举例说明:
int myDevide(int a,int b)
{
if(b == 0)
{
return -1;
}
return a/b;
}
int test01()
{
int a = 10;
int b = 0;
int ret = myDevide(a,b);
if(ret == -1)
return 0;
else
return ret;
}
以上操作的核心,就是防止程序的崩溃,一旦发现有除0操作,立即返回,防止内存溢出。
C++则用一下方式进行处理:
int myDevide(int a,int b)
{
if(b == 0)
{
throw -1;
}
return a/b;
}
int test01()
{
int a = 10;
int b = 0;
try
{
myDevide(a,b);
}
catch(int)
{
qDebug()<<"int异常捕获";
}
}
throw的是int类型,所以catch括号内的也要是int类型。
如果想捕获任意类型的异常,需要将catch改成如下情况
2捕获任意类型
try
{
//保护代码
}
catch(...)
{
qDebug()<<"任何异常捕获";
}
在异常声明的括号内使用省略号 ...,即可。
3异常的嵌套捕获
catch可以选择处理异常,也可以选择不处理
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
try
{
test01();
}
catch(...)
{
qDebug()<<"构造函数中的异常捕获";
}
}
MainWindow::~MainWindow()
{
delete ui;
}
int myDevide(int a,int b)
{
if(b == 0)
{
throw -1;
}
return a/b;
}
int test01()
{
int a = 10;
int b = 0;
try
{
myDevide(a,b);
}
catch(int)
{
qDebug()<<"int异常捕获";
}
catch(double)
{
qDebug()<<"double异常捕获";
}
catch(...)
{
qDebug()<<"...异常捕获";
}
}
构造函数里面调用test,test调用myDecide,myDecide出现异常,抛出,给test,test处理,则不说了,test也可以选择不处理,那么就会落到构造函数中的try和catch中进行处理。
以上代码的输出:
catch中不进行异常处理,直接写throw就行。
int Function_transfer(int a,int b)
{
if(b == 0)
{
throw xxx;
}
return qqq;
}
int Function()
{
int a = 10;
int b = 0;
try
{
Function_transfer(a,b);
}
catch(int)
{
throw;//可以选择不处理
qDebug()<<"int异常捕获";
}
}
如果所有的catch全部都throw, 那么程序必将崩溃。
异常抛出(throw)后,必须有人catch,没有,则会调用terminate函数,之后程序崩溃。
同样,也要注意,如果有多个catch,那么catch(...)必须在最后,否则会报错
4对自定义类型异常的捕获
定义一个类:
class myException
{
public:
void printError()
{
qDebug()<<"自定义异常";
}
};
具体操作:
int myDevide(int a,int b)
{
if(b == 0)
{
throw myException();//匿名对象,抛出对象,不起名字而已
}
return a/b;
}
void test01()
{
int a = 10;
int b = 0;
try
{
myDevide(a,b);
}
catch(int)
{
throw;
qDebug()<<"int异常捕获";
}
catch(double)
{
qDebug()<<"double异常捕获";
}
catch(myException e)
{
qDebug()<<"myException异常捕获";
e.printError();
}
catch(...)
{
qDebug()<<"...异常捕获";
}
}
throw的是一个自定义类型的匿名对象,显然,catch要用同样自定义类型的对象,并且在catch后利用这个对象调用这个类中本身自带的打印异常接口。
5栈解旋
自定义类如下:
class MyException
{
public:
MyException()
{
qDebug()<<"默认构造函数";
}
~MyException()
{
qDebug()<<"析构函数";
}
MyException(const MyException & c)
{
qDebug()<<"拷贝构造函数";
}
};
从try开始,所有栈上的对象,都会被抛出,除了throw的内容,如果也在栈上,那么是在catch完成后抛出,其他内容都是在try开始doWork后抛出。
void doWork()
{
qDebug()<<"栈解旋部分";
MyException e1;
MyException e2;
throw MyException();
}
void test()
{
try
{
doWork();
}
catch(MyException & e)
{
qDebug()<<"异常捕获";
}
}
6异常的接口声明
为了增强函数的可读性,可以在函数声明中列出可能抛出异常的所有类型,如;
void func() throw(A,B,C);
这个函数func能够且只能抛出类型A,B,C以及其子类型的异常。
如果函数声明中没有包含异常接口声明,则此函数可以抛出任何类型的异常,例如:void func()
一个不抛出任何类型异常的函数可声明为:void func() throw()
如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexcepted函数会被调用,然后调用terminate函数中断程序
先上面举例所述,throw各种各样的数据类型,那么如果要求,只能throw一个或多个类型,该如何增加这个约束条件:
void Function's name () throw(type of data 1,...,type of data n)
{
throw X;//must form data 1 to data n
}
如下:只能throw出去int类型的数据。
void function_Error() throw(int)
{
throw 1;
}
当然了,不写throw()可以抛出任意的数据类型。
只写throw(),不能抛出异常。
7异常变量的生命周期
自定义类:
class MyException
{
public:
MyException()
{
qDebug()<<"默认构造函数";
}
~MyException()
{
qDebug()<<"析构函数";
}
MyException(const MyException & c)
{
qDebug()<<"拷贝构造函数";
}
};
具体操作:
void doWork()
{
throw MyException();
}
void test()
{
try
{
doWork();
}
catch(MyException e)
{
qDebug()<<"异常捕获";
}
}
说明:
throw MyException();
匿名对象,调用构造函数。
而
try
{
doWork();
}
catch(MyException e)
{
qDebug()<<"异常捕获";
}
catch 的 MyException e,相当于函数参数, MyException e = 匿名对象。这种情况自然是会调用拷贝构造函数的,多了一份开销,此时只要使用引用即可。
void test()
{
try
{
doWork();
}
catch(MyException & e)
{
qDebug()<<"异常捕获";
}
}
我们再看下面这种情况:
把引用换成指针:
void doWork()
{
MyException e;
throw &e;
}
void test()
{
try
{
doWork();
}
catch(MyException * e)
{
qDebug()<<"异常捕获";
}
}
在doWork完成后,直接销毁了e,那么在catch中就没有办法再使用任何MyException的接口,因为已经被销毁了。
放在堆区:
void doWork()
{
MyException * e = new MyException;
throw e;
}
需要在catch中进行delete
void test()
{
try
{
doWork();
}
catch(MyException * e)
{
qDebug()<<"异常捕获";
delete e;
}
}
综上:使用引用最好
8C++标准的异常
和自定义异常的捕获很像,只是这个时候,程序内部写好了throw,只要你写好try和catch就行
(图源:菜鸟教程)
举例:最常见的指针越界问题
包含头文件:
#include<stdexcept>//trycatch
执行代码:
string str = "I see a monsters";
string::iterator S;
for(S = str.begin();S!=str.end();++S)
{
try
{
cout<<*S<<endl;
}
catch(out_of_range & e)
{
cout<<endl<<e.what()<<endl;
}
}
和自定义异常一样
try
{
//保护程序
}
catch(系统异常类名 Name_Object)
{
Name_Object.what();//调用what接口
}
在这里,what() 是异常类提供的一个公共方法。
9多态
看代码:父类引用指向子类对象
class BaseException
{
public:
virtual void printError()
{
qDebug()<<"空指针"<<endl;
}
};
class OutOfRangeExcept:public BaseException
{
public:
virtual void printError()
{
qDebug()<<"越界"<<endl;
}
};
void deWork()
{
//throw
throw OutOfRangeExcept();
}
void test01()
{
try
{
deWork();
}
catch(BaseException & e)
{
e.printError();
}
}
输出:
显然,进入catch的时候,BaseException的引用指向了OutOfExcept的匿名对象,父类指针(引用)指向子类对象,形成(动态)多态。