C++的异常处理机制

前言

  1. 异常是一种程序控制机制,与函数机制独立和互补:
    函数是一种以栈结构展开的上下函数衔接的程序控制系统,异常是另一种控制结构,它依附于栈结构,却可以同时设置多个异常类型作为网捕条件,从而以类型匹配在栈机制中跳跃回馈.
  2. 异常设计目的:
    栈机制是一种高度节律性控制机制,面向对象编程却要求对象之间有方向、有目的的控制传动,从一开始,异常就是冲着改变程序控制结构,以适应面向对象程序更有效地工作这个主题,而不是仅为了进行错误处理。
    异常设计出来之后,却发现在错误处理方面获得了最大的好处。

异常基本语法

异常基本语法

  1. 若有异常则通过throw操作创建一个异常对象并抛掷。
  2. 将可能抛出异常的程序段嵌在try块之中。控制通过正常的顺序执行到达try语句,然后执行try块内的保护段。
  3. 如果在保护段执行期间没有引起异常,那么跟在try块后的catch子句就不执行。程序从try块后跟随的最后一个catch子句后面的语句继续执行下去。
  4. catch子句按其在try块后出现的顺序被检查。匹配的catch子句将捕获并处理异常(或继续抛掷异常)。
  5. 如果匹配的处理器未找到,则运行函数terminate将被自动调用,其缺省功能是调用abort终止程序。
  6. 处理不了的异常,可以在catch的最后一个分支,使用throw语法,向上扔。

这是一个C++中异常的抛出机制:

#include <iostream>
using namespace std;
void divde(int x, int y)
{
	if (y == 0)
	{
		throw x;  // 当y == 0时,抛出异常
	}
	cout << "y/x的结果" << x/y << endl;
}

int main()
{

	try
	{
		divde(10, 2);
		divde(10, 0);
	}
	catch (int e)
	{
		cout << e << "被零整数" << endl;
	}
	catch (...) //其他种类的异常处理
	{
		cout << "未知异常" << endl;
	}
	return 0;
}

异常可以不处理,继续外抛。

#include <iostream>
using namespace std;
void divde(int x, int y)
{
	if (y == 0)
	{
		throw x;  // 当y == 0时,抛出异常
	}
	cout << "y/x的结果" << x/y << endl;
}
void mydivde(int x, int y)
{
	try 
	{
		divde(x, y);
	}
	catch (...)
	{
		cout << "我不处理异常" << endl;
		throw;
	}
}
int main()
{
	try
	{
		mydivde(100, 0);
	}
	catch (int e)
	{
		cout << e << "被零整数" << endl;
	}
	catch (...) //其他种类的异常处理
	{
		cout << "未知异常" << endl;
	}
	
	return 0;
}


#include <iostream>
using namespace std;


void testFunction1() {
	if (1) throw 1;
}
void testFunction2() {
	try 
	{
		testFunction1();
	}
	catch (char) /*异常时严格的按照类型匹配的*/
	{
		cout << "期待的异常char" << endl;
	}
}


int main()
{
	testFunction2();
	return 0;
}

throw 1将穿透函数testFunction1,testFunction2和main,抵达系统的最后一道防线——激发terminate函数(C++中,异常不可以忽略,当异常找不到匹配的catch字句时,会调用系统的库函数terminate(),该函数调用引起运行终止的abort函数。)最后一道防线的函数可以由程序员设置.从而规定其终止前的行为.
修改系统默认行为:

  • 可以通过set_terminate函数修改捕捉不住异常的默认处理器,从而使得发生捉不住异常时,被自定义函数处理:
  • void myTerminate(){cout<<“HereIsMyTerminate\n”;}
  • set_terminate(myTerminate);
  • set_terminate函数在头文件exception中声明,参数为函数指针void(*)().

栈解旋(unwinding)

异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反。这一过程称为栈的解旋(unwinding)。
案例:

#include <iostream>
using namespace std;

class MyException {};

class Test
{
public:
	Test(int a = 0, int b = 0)
	{
		this->a = a;
		this->b = b;
		cout << "Test 构造函数执行" << "a:" << a << " b: " << b << endl;
	}
	void printT()
	{
		cout << "a:" << a << " b: " << b << endl;
	}
	~Test()
	{
		cout << "Test 析构函数执行" << "a:" << a << " b: " << b << endl;
	}
private:
	int a;
	int b;
};

void myFunc() throw (MyException)
{
	Test t1(1, 2);
	Test t2(2, 3);
	cout << "要发生异常" << endl;
	throw 1; 

}

int main()
{
	//异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象,
	//都会被自动析构。析构的顺序与构造的顺序相反。
	//这一过程称为栈的解旋(unwinding)
	try
	{
		myFunc();
	}
	catch (int e) 
	{
		cout << "接收到int类型异常" << endl;
	}
	catch (...)
	{
		cout << "未知类型异常" << endl;
	}
	return 0;
}

测试结果:这个也很好理解,throw异常相当这个函数要返回了,把异常信息返回给捕获函数。那么就要把在这个函数压栈产生的变量给进行栈解旋。
在这里插入图片描述

异常接口声明

  1. 为了加强程序的可读性,可以在函数声明中列出可能抛出的所有异常类型,例如:void func() throw (A, B, C , D); //这个函数func()能够且只能抛出类型A B C D及其子类型的异常。
  2. 如果在函数声明中没有包含异常接口声明,则次函数可以抛掷任何类型的异常,例如: void func();
  3. 一个不抛掷任何类型异常的函数可以声明为:void func() throw();
  4. 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexpected函数会被调用,该函数默认行为调用terminate函数中止程序。

异常类型和异常变量的生命周期

  1. throw的异常是有类型的,可以使,数字、字符串、类对象。
  2. throw的异常是有类型的,catch严格按照类型进行匹配。
  3. 注意 异常对象的内存模型

下面我比较传统的错误处理机制和C++的异常处理机制
传统的错误处理机制

#include <iostream>
using namespace std;
// 传统的错误处理机制
int my_strcpu(char *dest, char *soucre)
{   
	if (dest == nullptr)
	{   
		return 1;
	}
	if (soucre == nullptr)
	{
		return 2;
	}
	// 比如做一个copy的数据限定
	if (*soucre == 'a')
	{
		return 3;
	}
	while (*soucre != '\0')
	{
		*dest++ = *soucre++;
	}
	*dest = '\0';
	return 0;
}

int main()
{   
	int  ret;
	char destBuff[1024] = {0};
	char sourceBuff[] = "123456789";
	ret = my_strcpu(destBuff,sourceBuff);
	if (ret != 0)
	{
		switch (ret)
		{
		case 1: cout << "destBuff is error" << endl;
			break;
		case 2: cout << "sourceBuff is error" << endl;
			break;
		case 3: cout << "copy is error" << endl;
			break;
		default:cout << "未知错误" << endl;
			break;
		}
	}
	cout << destBuff << endl;
	return 0;
}

异常处理机制

#include <iostream>
using namespace std;
// C++的异常错误处理机制
class  BadDest {};
class  BadSource {};
class  BadCopy {};
void my_strcpu(char* dest, char* soucre)
{
	if (dest == nullptr)
	{
		throw BadDest();
	}
	if (soucre == nullptr)
	{
		throw BadSource();
	}
	// 比如做一个copy的数据限定
	if (*soucre == 'a')
	{
		throw BadCopy();
	}
	while (*soucre != '\0')
	{
		*dest++ = *soucre++;
	}
	*dest = '\0';
}

int main()
{
	int  ret;
	char destBuff[1024] = { 0 };
	char sourceBuff[] = "a123456789";
	
 
	try
	{
		my_strcpu(destBuff, sourceBuff);
	}
	catch (BadDest e)
	{
		cout << "destBuff is error" << endl;
	}
	catch (BadSource e)
	{
		cout << "sourceBuff is error" << endl;
	}
	catch (BadCopy e)
	{
		cout << "copy is error" << endl;
	}
	catch (...)
	{
		cout << "未知错误" << endl;
	}

	cout << destBuff << endl;
	return 0;
}

这个异常会throw一个匿名对象,那么这个有一个问题就值得谈论?这个匿名对象是copy给e还是e就是这个匿名对象了?先说结论:这个匿名对象是拷贝给e的。
测试代码

#include <iostream>
using namespace std;
// C++的异常错误处理机制
class  BadDest {};
class  BadSource {};
class  BadCopy 
{
public:
	BadCopy() 
	{
		cout << "BadCopy 构造函数 do" << endl;
	}
	BadCopy(const BadCopy &obj)
	{
		cout << "BadCopy 拷贝构造函数 do" << endl;
	}
	~BadCopy()
	{
		cout << "BadCopy 析构函数 do" << endl;
	}
};
void my_strcpu(char* dest, char* soucre)
{
	if (dest == nullptr)
	{
		throw BadDest();
	}
	if (soucre == nullptr)
	{
		throw BadSource();
	}
	// 比如做一个copy的数据限定
	if (*soucre == 'a')
	{   
		cout << "开始throw BadCopy" << endl;
		throw BadCopy();
	}
	while (*soucre != '\0')
	{
		*dest++ = *soucre++;
	}
	*dest = '\0';
}

int main()
{
	int  ret;
	char destBuff[1024] = { 0 };
	char sourceBuff[] = "a123456789";
	
 
	try
	{
		my_strcpu(destBuff, sourceBuff);
	}
	catch (BadDest e)
	{
		cout << "destBuff is error" << endl;
	}
	catch (BadSource e)
	{
		cout << "sourceBuff is error" << endl;
	}
	catch (BadCopy e)
	{
		cout << "copy is error" << endl;
	}
	catch (...)
	{
		cout << "未知错误" << endl;
	}

	cout << destBuff << endl;
	return 0;
}

测试结果如下:
在这里插入图片描述
进一步讨论如果这个是引用的话,匿名对象是copy还是直接使用throw出来的那个对象。

#include <iostream>
using namespace std;
// C++的异常错误处理机制
class  BadDest {};
class  BadSource {};
class  BadCopy 
{
public:
	BadCopy() 
	{
		cout << "BadCopy 构造函数 do" << endl;
	}
	BadCopy(const BadCopy &obj)
	{
		cout << "BadCopy 拷贝构造函数 do" << endl;
	}
	~BadCopy()
	{
		cout << "BadCopy 析构函数 do" << endl;
	}
};
void my_strcpu(char* dest, char* soucre)
{
	if (dest == nullptr)
	{
		throw BadDest();
	}
	if (soucre == nullptr)
	{
		throw BadSource();
	}
	// 比如做一个copy的数据限定
	if (*soucre == 'a')
	{   
		cout << "开始throw BadCopy" << endl;
		throw BadCopy();
	}
	while (*soucre != '\0')
	{
		*dest++ = *soucre++;
	}
	*dest = '\0';
}

int main()
{
	int  ret;
	char destBuff[1024] = { 0 };
	char sourceBuff[] = "a123456789";
	
 
	try
	{
		my_strcpu(destBuff, sourceBuff);
	}
	catch (BadDest e)
	{
		cout << "destBuff is error" << endl;
	}
	catch (BadSource e)
	{
		cout << "sourceBuff is error" << endl;
	}
	catch (BadCopy &e)
	{
		cout << "copy is error" << endl;
	}
	catch (...)
	{
		cout << "未知错误" << endl;
	}

	cout << destBuff << endl;
	return 0;
}

测试结果:
在这里插入图片描述
更进一步讨论如果这个是引用的话,匿名对象是copy还是直接使用throw出来的那个对象。

大家可以试试,你使用指针的类型接收上述代码throw的异常是接受不到,还有一个小细节,catch (BadCopy &e)catch (BadCopy e)不可以同时存在,但是catch (BadCopy &e)catch (BadCopy *e)可以同时存在,catch (BadCopy e)catch (BadCopy *e)也是可以的。那想要让catch (BadCopy *e)可以接受的到,那么throw就必须抛出一个地址,哪只要代码这么 throw &(BadCopy());改一下就可以了

异常生命周期总结:

结论1:如果在接受异常的时候使用一个异常变量,则copy构造异常变量。
结论2:如果在接受异常的时候使用一个异常引用,会使用throw的那个匿名对象,所以引用e还是那个匿名对象
结论3:指针和引用/变量可以同时存在,但是引用和变量不能写在一块。
结论4:若想使用指针接受异常,throw的值是一个地址,这样子写还可能导致野指针的问题!!!地址throw出来后析构了,但是指针还没有指向nullptr。好的方法是throw new (BadCopy());但是记得delete e在捕获中避免内存泄漏。

最好的方法:引用接,简单方便安全

异常的层次结构(继承在异常中的应用)

  • 异常是类 – 创建自己的异常类
  • 异常派生
  • 异常中的数据:数据成员
  • 按引用传递异常
  • 在异常中使用虚函数
    案例:
    案例:设计一个数组类 MyArray,重载[]操作,
    数组初始化时,对数组的个数进行有效检查
    1) index<0 抛出异常eNegative
    2) index = 0 抛出异常 eZero
    3) index>1000抛出异常eTooBig
    4) index<10 抛出异常eTooSmall
    5) eSize类是以上类的父类,实现有参数构造、并定义virtual void printErr()输出错误。
#include <iostream>
using namespace std;

class MyArrary
{
public:
	MyArrary(int arrayLength);
	~MyArrary();
	int& operator [](int index);
	int getArraryLength();
//异常处理类
public:
	class eSize
	{
	public:
		eSize(int size)
		{
			mySize = size;
		}
		virtual	void printError() 
		{
			cout << "size" << mySize << endl;
		}
	protected:
		int mySize;
	};
	class eNegative : public eSize
	{
	public:
		eNegative(int size) : eSize(size)
		{
		}
		virtual	void printError()
		{
			cout << "eNegative is error ArrayLength is " << mySize << endl;
		}
	};
	class eZero : public eSize
	{
	public:
		eZero(int size) : eSize(size)
		{
		}
		virtual	void printError()
		{
			cout << "eZero is error ArrayLength is " << mySize << endl;
		}
	};
	class eTooBig : public eSize
	{
	public:
		eTooBig(int size) : eSize(size)
		{
		}
		virtual	void printError()
		{
			cout << "eTooBig is error ArrayLength is " << mySize << endl;
		}
	};
	class eTooSmall : public eSize
	{
	public:
		eTooSmall(int size) : eSize(size)
		{
		}
		virtual	void printError()
		{
			cout << "eTooSmall is error ArrayLength is " << mySize << endl;
		}
	};



protected:
private:
	int arrayLength;
	int* mySpace;
};


MyArrary::MyArrary(int arrayLength)
{
	if (arrayLength < 0)
	{
		throw eNegative(arrayLength);
	}
	else if (arrayLength == 0)
	{
		throw eZero(arrayLength);
	}
	else if (arrayLength > 1000)
	{
		throw eTooBig(arrayLength);
	}
	else if (arrayLength < 10)
	{
		throw eTooSmall(arrayLength);
	}
	this->arrayLength = arrayLength;
	mySpace = new int[arrayLength];
}
MyArrary::~MyArrary()
{
	if (mySpace != nullptr)
	{
		delete[] mySpace;
		mySpace = nullptr;
		arrayLength = 0;
	}
	arrayLength = 0;

}
int& MyArrary::operator [](int index)
{
	return mySpace[index];
}

int MyArrary::getArraryLength()
{
	return arrayLength;
}

int main()
{
	//1)	index < 0 抛出异常eNegative
	//	2)	index = 0 抛出异常 eZero
	//	3) index>1000抛出异常eTooBig
	//	4) index < 10 抛出异常eTooSmall
	//	5) eSize类是以上类的父类,实现有参数构造、并定义virtual void printErr()输出错误。
		try
	{
		MyArrary a(0);
		for (int i = 0; i < a.getArraryLength(); i++)
		{
			a[i] = i;
			cout << a[i] << endl;

		}
	}
		catch (MyArrary::eSize & e)
		{
			e.printError(); // 利用多态
		}
	catch (...)
	{
		cout << "未知异常" << endl;
	}

	return 0;
}

可以使用多态和异常相结合大大的提高异常处理的灵活性。

标准程序库异常

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

使用标准异常库的案例:

#include <iostream>
#include <stdexcept>
using namespace std;

class Teacher
{
public:
	Teacher(int age)
	{
		if (age > 100)
		{
			throw out_of_range("年龄太大");
		}
		this->age = age;
	}
private:
	int age;

};


int main()
{
	try
	{
		Teacher t1(101);
	}
	catch (out_of_range e)
	{
		cout << e.what() << endl;
	}
	catch (...)
	{
		cout << "未知异常" << endl;
	}
 return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值