概述:
处理一个程序异常的基本思想是让一个函数在发现了自己无法处理的错误时抛出(throw)一个异常,然后它 的(直接或者间接)调用者能够处理这个问题。也就是《C++ primer》中说的:将 问题检测和问题处理相分离。 一种思想:在所有支持异常处理的编程语言中(例 如 java),要认识到的一个思想:在异常处理过程中,由问题检测代码可以抛出一 个对象给问题处理代码,通过这个对象的类型和内容,实际上完成了两个部分的通 信,通信的内容是“出现了什么错误”。当然,各种语言对异常的具体实现有着或多 或少的区别,但是这个通信的思想是不变的。 一句话:异常处理就是处理程序中的错误。所谓错误是指在程序运行的过程中发生 的一些异常事件(如:除 0 溢出,数组下标越界,所要读取的文件不存在,空指 针,内存不足等等)。
C语言中异常处理方法:
在C语言中,对错误的处 理总是围绕着两种方法:一是使用整型的返回值标识错误;二是使用 errno 宏(可 以简单的理解为一个全局整型变量)去记录错误。当然 C++中仍然是可以用这两种 方法的。 这两种方法最大的缺陷就是会出现不一致问题。例如有些函数返回 1 表 示成功,返回 0 表示出错;而有些函数返回 0 表示成功,返回非 0 表示出错。 还 有一个缺点就是函数的返回值只有一个,你通过函数的返回值表示错误代码,那么 函数就不能返回其他的值。
如下面例子:
int mydive(int a, int b)
{
if (b == 0)
return -1;
return a / b;
}
/*如果除出来的结果正好等于-1就出现了问题*/
void test01()
{
int ret = mydive(1, -1);
if (ret == -1) cout << "除数为0" << endl;
}
int main()
{
test01();
return 0;
}
如果异常时候返回的值和函数正常时候的值一样的时候就会出现一些问题。
C++异常的基本语法:
#include <iostream>
#include <string>
using namespace std;
int mydive(int a, int b)
{
if (b == 0)
throw 'a';//抛出类型
return a / b;
}
void test01()
{
//尝试捕获异常
try
{
mydive(2, 0);
}
/*try和catch必须同时存在
抛出异常类型和捕获类型异常不一致时候出错
如果没有捕获到抛出的异常程序会终止*/
catch (char)
{
//cout << "捕获了一个char类型的异常" << endl;
throw '1';
}
}
int main()
{
try
{
test01();
}
catch (char)
{
cout << "捕获了一个char类型的异常" << endl;
}
return 0;
}
小总结:
若有异常则通过 throw 操作创建一个异常对象并抛出。 将可能抛出异常的 程序段(比如函数my_dive)放到 try 块之中。 如果在 try 段执行期间没有引起异常,那么跟在 try 后面 的 catch 字句就不会执行。 catch 子句会根据出现的先后顺序被检查,匹配的 catch 语句捕获并处理异常(或继续抛出异常) 如果匹配的处理未找到,则运行函数 terminate 将自动被调用,其缺省功能调用 abort 终止程序。 处理不了的异常,可 以在 catch 的最后一个分支,使用 throw,向上抛。 c++异常处理使得异常的引发和异常的处理不必在一个函数中,这样底层的函数可 以着重解决具体问题,而不必过多的考虑异常的处理。上层调用者可以在适当的位 置设计对不同类型异常的处理。
注意:
1,try和catch是同时存在的。
2.抛出的类型和捕捉的类型要严格匹配(整型对整型,不匹配无法捕获)。
为了防止没有捕获到对应的类型;使用以下代码防止程序终止;
catch (...)
{
cout << "捕获了其他类型" << endl;
}
栈解旋
异常被抛出后,从进入 try 块起,到异常被抛掷前,这期间在栈上构造的所有对 象,都会被自动析构。析构的顺序与构造的顺序相反,这一过程称为栈的解旋 (unwinding)
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
Person(string name)
{
this->name = name;
}
~Person()
{
cout << "析构函数调用" << endl;
}
string name;
};
void fun()
{
Person p2("test02");
Person p3("test03");
cout << "001" << endl;
throw 1;
}
void test01()
{
try
{
Person p1("test01");
fun();
}
catch (int)
{
cout << "002" << endl;
cout << "捕获int类型异常" << endl;
}
}
int main()
{
test01();
return 0;
}
执行结果如下:
对于在try和throw之间定义的对象,throw之后会被释放(析构)
异常接口申明:
为了加强程序的可读性,可以在函数声明中列出可能抛出异常的所有类型,例 如:void func() throw(A,B,C);这个函数 func 能够且只能抛出类型 A,B,C 及其子类型 的异常。 如果在函数声明中没有包含异常接口声明,则此函数可以抛任何类型的 异常,例如:void func() 一个不抛任何类型异常的函数可声明为:void func() throw() 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexcepted 函数会被 调用,该函数默认行为调用 terminate 函数中断程序。
#include <iostream>
#include <string>
using namespace std;
void test01() throw(char)
{
throw '1';
}
void test02() throw(int)
{
throw 1;
}
void test()
{
try
{
//test01();
test02();
}
catch (int)
{
cout << "捕获到了int类型异常" << endl;
}
catch (char)
{
cout << "捕获到了char类型异常" << endl;
}
}
int main()
{
test();
return 0;
}
注:异常接口声明只是在函数后面加上了可以抛出的异常类型。
异常变量声明周期:
throw 的异常是有类型的,可以是数字、字符串、类对象。 throw 的异常是有类 型的,catch 需严格匹配异常类型。
如以下代码:
class Myexception
{
public:
Myexception()
{
cout << "构造函数" << endl;
}
~Myexception()
{
cout << "析构函数" << endl;
}
void error()
{
cout << "test" << endl;
}
};
void fun_test()
{
Myexception p1;
//throw Myexception();//匿名对象
throw p1;//p1生命周期在throw之后
}
void test01()
{
try
{
fun_test();
}
catch (Myexception& p)
{
p.error();
}
}
/*如果抛出匿名对象,生命周期在catch里面*/
int main()
{
test01();
return 0;
}
对于自定义对象来说,如果抛出的是一个匿名对象,则生命周期在catch里面,而如果是一个对象的话抛出对象的声明周期是在throw之后。