C++异常
前言:
异常处理就是处理程序中的错误。所谓错误是指在程序运行的过程中发生的一些异常事件(如:除0溢出,数组下标越界,所要读取的文件不存在,空指针,内存不足等等)
在对C语言的学习中,我们常常对错误的处理围绕着两种方法:一种是使用整型的返回值标识错误;二是使用errno宏(可以简单的理解为一个全局整型变量)去记录错误。
但这两种方法最大的缺陷就是会出现不一致问题。例如有些函数返回1表示成功,返回0标识错误;而有些函数返回0标识成功,返回非0表示错误。还有一个缺陷是函数的返回值只有一个,你通过函数的返回值表示错误代码,那么函数就不能返回其他的值。
C++相比C语言在异常处理上有哪些优势呢?
我们可先看看下面异常语法的代码再进行总结。
//异常的基本语法
int func(int a, int b)
{
if (b == 0)
{
//第二步:
throw 10; //抛出一个int类型的异常
}
return a / b;
}
void test()
{
int a = 10;
int b = 0;
//把有可能出现异常的代码块放到try中
try
{
func(a, b); //第一步
}
catch (int) //第三步
{
cout << "接收一个int类型的异常" << endl;
}
}
回到上面的问题
- 函数的返回值可以忽略,但异常不可忽略。如果程序出现异常,但是没有被捕获,程序就会终止,这多少会促使程序员开发出来的程序更健壮一点。而如果使用C语言的error宏或者函数返回值,调用者都有可能忘记检查,从而没有对错误进行处理,结果造成程序莫名其面的终止或出现错误的结果。
- 整型返回值没有任何语义信息。而异常却包含语义信息,有时你从类名就能够体现出来。
- 整型返回值缺乏相关的上下文信息。异常作为一个类,可以拥有自己的成员,这些成员就可以传递足够的信息。
- 异常处理可以在调用跳级。这是一个代码编写时的问题:假设在有多个函数的调用栈中出现了某个错误,使用整型返回码要求你在每一级函数中都要进行处理。而使用异常处理的栈展开机制,只需要在一处进行处理就可以了,不需要每级函数都处理。
异常具有严格的类型匹配
异常机制和函数机制互不干涉,但是捕捉方式是通过严格类型匹配。
int func(int a, int b)
{
if (b == 0)
{
throw 20.2f;
}
return a / b;
}
void test()
{
int a = 10;
int b = 0;
try
{
func(a, b);
}
catch (double s)
{
cout << "接收一个double类型的异常" << endl;
}
catch (char)
{
cout << "接收一个char类型的异常" << endl;
}
catch (...) //接收其他类型的异常
{
cout << "接收一个其他类型的异常" << endl;
}
}
栈解旋(unwinding)
异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反,这一过程称为栈的解旋(unwinding).
class Maker
{
public:
Maker()
{
cout << "Maker的构造" << endl;
}
Maker(const Maker& m)
{
cout << "Maker的拷贝构造" << endl;
}
~Maker()
{
cout << "Maker的析构" << endl;
}
};
void func()
{
//在抛出异常的函数中,如果抛出异常之后,但函数没有结束,这时,栈上申请的对象都会被释放
//这就叫栈解旋
Maker m;
throw m;//这个m是Maker m拷贝构造的
cout << "func函数结束" << endl;
}
void test()
{
try
{
func();
cout << "func()代码后" << endl;
}
catch (Maker)
{
cout << "接收一个Maker类型的异常" << endl;
}
}
运行结果:
异常接口声明
- 为了加强程序的可读性,可以在函数声明中列出可能抛出异常的所有类型,例如:void func() throw(A,B,C);这个函数func能够且只能抛出类型A,B,C及其子类型的异常。
- 如果在函数声明中没有包含异常接口声明,则此函数可以抛任何类型的异常,例如:void func()
- 一个不抛任何类型异常的函数可声明为:void func() throw()
- 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexcepted函数会被调用,该函数默认行为调用terminate函数中断程序。
void func() throw(int, char) //只允许抛出 int 或者 char异常
{
throw 10; //抛出一个double类型的异常
}
void test()
{
try
{
func();
}
catch (int)
{
cout << "抛出一个int型的异常";
}
catch (...)
{
cout << "抛出一个其他型的异常";
}
}
运行结果:
异常变量周期
产生三个对象
class Maker
{
public:
Maker()
{
cout << "Maker的构造" << endl;
}
Maker(const Maker& m)
{
cout << "Maker的拷贝构造" << endl;
}
~Maker()
{
cout << "Maker的析构" << endl;
}
};
//产生三个对象
void func1()
{
Maker m;//第一个对象,在异常接收前被释放
throw m;//第二个对象,是第一个对象拷贝过来的
}
void test01()
{
try
{
func1();
}
catch (Maker m1)//第三个对象,是第二个对象拷贝过来的
{
cout << "接收一个Maker类型的异常" << endl;
//第二个和第三个对象在catch结束时释放
}
}
产生两个对象
void func2()
{
//第一个对象
throw Maker();//匿名对象
}
void test02()
{
try
{
func2();
}
catch (Maker m1)//第二个对象
{
cout << "接收一个Maker类型的异常" << endl;
//第一个和第二个对象在catch结束时释放
}
}
产生一个对象
void func3() //常用这种方式
{
throw Maker();//匿名对象
}
void test03()
{
try
{
func3();
}
catch (Maker& m1)
{
cout << "接收一个Maker类型的异常" << endl;
}
}
异常的多态使用
//异常的基类
class Father
{
public:
virtual void printM()
{
}
};
//1、有继承
class SonNULL :public Father
{
public:
virtual void printM() //2、重写父类的虚函数
{
cout << "空指针的异常" << endl;
}
};
class SonOut :public Father
{
public:
virtual void printM()
{
cout << "越位溢出" << endl;
}
};
void func(int a,int b)
{
if (a == 0)
{
throw SonNULL();
}
if (b == 0)
{
throw SonOut();
}
}
void test()
{
int a = 0;
int b = 10;
try
{
func(a, b);
}
catch (Father& f) //3、父类引用指向子类对象
{
f.printM();
}
}