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;
	}
}

回到上面的问题

  1. 函数的返回值可以忽略,但异常不可忽略。如果程序出现异常,但是没有被捕获,程序就会终止,这多少会促使程序员开发出来的程序更健壮一点。而如果使用C语言的error宏或者函数返回值,调用者都有可能忘记检查,从而没有对错误进行处理,结果造成程序莫名其面的终止或出现错误的结果。
  2. 整型返回值没有任何语义信息。而异常却包含语义信息,有时你从类名就能够体现出来。
  3. 整型返回值缺乏相关的上下文信息。异常作为一个类,可以拥有自己的成员,这些成员就可以传递足够的信息。
  4. 异常处理可以在调用跳级。这是一个代码编写时的问题:假设在有多个函数的调用栈中出现了某个错误,使用整型返回码要求你在每一级函数中都要进行处理。而使用异常处理的栈展开机制,只需要在一处进行处理就可以了,不需要每级函数都处理。

异常具有严格的类型匹配

异常机制和函数机制互不干涉,但是捕捉方式是通过严格类型匹配。

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;
	}
}

运行结果:

c++学习笔记——模板和IO(二)

异常接口声明

  1. 为了加强程序的可读性,可以在函数声明中列出可能抛出异常的所有类型,例如:void func() throw(A,B,C);这个函数func能够且只能抛出类型A,B,C及其子类型的异常。
  2. 如果在函数声明中没有包含异常接口声明,则此函数可以抛任何类型的异常,例如:void func()
  3. 一个不抛任何类型异常的函数可声明为:void func() throw()
  4. 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexcepted函数会被调用,该函数默认行为调用terminate函数中断程序。
void func() throw(int, char)	//只允许抛出 int 或者 char异常
{
	throw 10;	//抛出一个double类型的异常
}
void test()
{
    try
    {
        func();
    }
    catch (int)
    {
        cout << "抛出一个int型的异常";
    }
    catch (...)
    {
        cout << "抛出一个其他型的异常";
    }
}

运行结果:

c++学习笔记——模板和IO(二)

异常变量周期

产生三个对象

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();
	}
}