C++-异常机制

一、异常处理机制定义

异常无处不在,程序随时可能误入歧途!C++ 提出了新的异常处理机制!

异常是一种程序控制机制,与函数机制互补
函数是一种以栈结构展开的上下函数衔接的程序控制系统,异常是另一种控制结构,它可以在出现“意外”时中断当前函数,并以某种机制(类型匹配)回馈给隔代的调用者相关的信息.

二、传统错误处理机制

通过函数返回值来处理错误。
实现文件的二进制拷贝:

#include 
#include 

#define BUFSIZE 1024

//实现文件的二进制拷贝
int copyfile(const char *dest,const char *src){
	FILE *fp1 = NULL, *fp2 = NULL;

	//rb 只读方式打开一个二进制文件,只允许读取数据
	fopen_s(&fp1, src, "rb");

	if(fp1 == NULL){
		return -1;
	}

	//wb 以只写的方式打开或新建一个二进制文件,只允许写数据。
	fopen_s(&fp2, dest, "wb");
	if(fp2 == NULL){
		return -2;
	}

	char buffer[BUFSIZE];
	int readlen, writelen;

	//如果读到数据,则大于0
	while( (readlen = fread(buffer, 1, BUFSIZE, fp1)) > 0 ){
		writelen = fwrite(buffer, 1, readlen, fp2);
		if(readlen != writelen){
			return -3 ;
		}
	}

	fclose(fp1);
	fclose(fp2);
	return 0;
}

void main(){
	int ret = 0;
	ret = copyfile("c:/test/dest.txt", "c:/test/src.txt");

	if(ret != 0){
		switch(ret){
		case -1:
			printf("打开源文件失败!\n");
			break;
		case -2:
			printf("打开目标文件失败!\n");
			break;
		case -3:
			printf("拷贝文件时失败!\n");
			break;
		default:
			printf("出现未知的情况!\n");
			break;
		}
	}
	system("pause");
}

三、异常处理基本语法

  • 异常发生第一现场,抛出异常

void function( ){
//… …
throw 表达式; //表达式可以为空
//… …
}

  • 在需要关注异常的地方,捕捉异常

try{ //保护段(程序执行段),在需要捕捉异常的地方,将可能抛出异常的程序段嵌在try块之中
//程序
function();
//程序
}catch(异常类型声明){
//… 异常处理代码 …
}catch(异常类型 形参){
//… 异常处理代码 …
}catch(){ //其它异常类型
//
}

说明:
异常类型声明,如:int
异常类型 形参,如:int error 表示错误由error来接收
其他类型异常,是:…表示类型通配

注意事项:

  1. 通过throw操作创建一个异常对象并抛掷

  2. 在需要捕捉异常的地方,将可能抛出异常的程序段嵌在try块之中

  3. 按正常的程序顺序执行到达try语句,然后执行try块{}内的保护段

  4. 如果在保护段执行期间没有引起异常,那么跟在try块后的catch子句就不执行,程序从try块后跟随的最后一个catch子句后面的语句继续执行下去

  5. catch子句按其在try块后出现的顺序被检查,匹配的catch子句将捕获并按catch子句中的代码处理异常(或继续抛掷异常)

  6. 如果没有找到匹配,则缺省功能是调用abort终止程序。

提示:处理不了的异常,我们可以在catch的最后一个分支,使用throw语法,继续向调用者throw。

// demo 15-16  
#include 
#include 
#include 

using namespace std;

#define BUFSIZE 1024

//实现文件的二进制拷贝
int copyfile2(char *dest, char *src){
	FILE *fp1 = NULL, *fp2 = NULL;

	//通过throw操作创建一个异常对象并抛掷
	throw 0.01f;
	//rb 只读方式打开一个二进制文件,只允许读取数据
	fopen_s(&fp1, src, "rb");

	if(fp1 == NULL){
		throw new string("文件不存在");
	}

	//wb 以只写的方式打开或新建一个二进制文件,只允许写数据。
	fopen_s(&fp2, dest, "wb");
	if(fp2 == NULL){
		throw -2;
	}

	char buffer[BUFSIZE];
	int readlen, writelen;

	//如果读到数据,则大于0
	while( (readlen = fread(buffer, 1, BUFSIZE, fp1)) > 0 ){
		writelen = fwrite(buffer, 1, readlen, fp2);
		if(readlen != writelen){
			throw -3 ;
		}
	}

	fclose(fp1);
	fclose(fp2);
	return 0;
}

int copyfile1(char *dest, char *src){
	try{
		copyfile2(dest, src);
	}catch(float e){
		//throw ;
		printf("copyfile1 - catch ...\n");

		//提示:处理不了的异常,我们可以在catch的最后一个分支,使用throw语法,继续向调用者throw。
		throw ;
	}

	return 0;
}

void main(){
	int ret = 0;

	//在需要捕捉异常的地方,将可能抛出异常的程序段嵌在try块之中
	//按正常的程序顺序执行到达try语句,然后执行try块{}内的保护段
	//如果在保护段执行期间没有引起异常,那么跟在try块后的catch子句就不执行,程序从try块后跟随的最后一个catch子句后面的语句继续执行下去
	try{//保护段
		printf("开始执行 copyfile1...\n");
		ret = copyfile1("c:/test/dest.txt", "c:/test/src.txt");
		printf("执行 copyfile1 完毕\n");
		
	 //catch子句按其在try块后出现的顺序被检查,匹配的catch子句将捕获并按catch子句中的代码处理异常(或继续抛掷异常)
	}catch(int error){
		printf("出现异常啦!%d\n", error);
	}catch(string *error){
		printf("捕捉到字符串异常:%s\n", error->c_str());
		delete error;
	}catch(float error){
		printf("出现异常啦!%f\n", error);
	}catch(...){
		printf("catch ...\n");
	}
	//如果异常没有找到匹配,则缺省功能是调用abort终止程序。

	system("pause");
}

四、异常接口声明

可以在函数声明中列出可能抛出的所有异常类型,加强程序的可读性。
如:
int copyfile2(char *dest, char *src) throw (float, string *, int)

  1. 对于异常接口的声明,在函数声明中列出可能抛出的所有异常类型
  2. 如果没有包含异常接口声明,此函数可以抛出任何类型的异常
  3. 如果函数声明中有列出可能抛出的所有异常类型,那么抛出其它类型的异常将可能导致程序终止
  4. 如果一个函数不想抛出任何异常,可以使用 throw () 声明(以警告方式出现)

五、异常类型和生命周期

传统的异常处理是一层一层返回,而异常处理是错误直达。
C++-异常机制_第1张图片

1.throw基本数据类型

基本类型包括如int、float、double等型,此时和函数返回传值是一样的。

#include 
#include 
#include 

using namespace std;

#define BUFSIZE 1024


//实现文件的二进制拷贝

//第一种情况,throw 普通类型,和函数返回传值是一样的
int copyfile2(char *dest, char *src){
	FILE *fp1 = NULL, *fp2 = NULL;

	//rb 只读方式打开一个二进制文件,只允许读取数据
	fopen_s(&fp1, src, "rb");

	if(fp1 == NULL){
		//int ret = -1;
		char ret = 'a';
		throw ret;
	}

	//wb 以只写的方式打开或新建一个二进制文件,只允许写数据。
	fopen_s(&fp2, dest, "wb");
	if(fp2 == NULL){
		throw -2;
	}

	char buffer[BUFSIZE];
	int readlen, writelen;

	//如果读到数据,则大于0
	while( (readlen = fread(buffer, 1, BUFSIZE, fp1)) > 0 ){
		writelen = fwrite(buffer, 1, readlen, fp2);
		if(readlen != writelen){
			throw -3 ;
		}
	}

	fclose(fp1);
	fclose(fp2);
	return 0;
}

int copyfile1(char *dest, char *src){
	return copyfile2(dest, src);
}

void main(){
	int ret = 0;

	
	try{//保护段
		//printf("开始执行 copyfile1...\n");
		ret = copyfile1("c:/test/dest.txt", "c:/test/src.txt");
		//printf("执行 copyfile1 完毕\n");
		
	}catch(int error){
		printf("出现异常啦!%d\n", error);
	}catch(char error){
		printf("出现异常啦!%c\n", error);
	}

	system("pause");
}  

2.throw 字符串类型

throw 字符串类型,实际抛出的指针,而且,修饰指针的const 也要严格进行类型匹配

#include 
#include 
#include 

using namespace std;

#define BUFSIZE 1024

//第二种情况,throw 字符串类型,实际抛出的指针,而且,修饰指针的const 也要严格进行类型匹配
int copyfile3(const char* dest,const char* src) {
	FILE* fp1 = NULL, * fp2 = NULL;

	//rb 只读方式打开一个二进制文件,只允许读取数据
	fopen_s(&fp1, src, "rb");

	if (fp1 == NULL) {
		const char* error = "你的源文件打开有问题";
		printf("throw 前,error 的地址:%p\n", error);
		throw error;
	}

	//wb 以只写的方式打开或新建一个二进制文件,只允许写数据。
	fopen_s(&fp2, dest, "wb");
	if (fp2 == NULL) {
		throw - 2;
	}

	char buffer[BUFSIZE];
	int readlen, writelen;

	//如果读到数据,则大于0
	while ((readlen = fread(buffer, 1, BUFSIZE, fp1)) > 0) {
		writelen = fwrite(buffer, 1, readlen, fp2);
		if (readlen != writelen) {
			throw - 3;
		}
	}

	fclose(fp1);
	fclose(fp2);
	return 0;
}

int copyfile1(const char* dest, const char* src) {
	return copyfile3(dest, src);
}

void main() {
	int ret = 0;

	try {//保护段
		//printf("开始执行 copyfile1...\n");
		ret = copyfile1("D:/code/dest.txt", "D:/code/src.txt");
		//printf("执行 copyfile1 完毕\n");

	}
	catch (int error) {
		printf("出现异常啦!%d\n", error);
	}
	catch (char error) {
		printf("出现异常啦!%c\n", error);
	}
	//不会跳到这个,因为char*与string类型不一样
	catch (string error) {
		printf("出现异常啦!%s\n", error.c_str());	//要转为C语言型的字符串才可以
	}
	catch (const char* error) {	//严格类型匹配
		printf("出现异常啦(char *)!%s (地址:%p)\n", error, error);
	}
	catch (...) {
		printf("没捉到具体的异常类型\n");
	}

	system("pause");
}

3.throw 类对象类型异常

throw 类类型,最佳的方式是使用引用类型捕捉,抛出匿名对象
如果是动态分配的对象,直接抛出其指针
注意:引用和普通的形参传值不能共存

#include 
#include 
#include 

using namespace std;

#define BUFSIZE 1024

class ErrorException {
public:
	ErrorException() {
		id = 0;
		printf("ErrorException  构造!\n");
	}

	~ErrorException() {
		printf("ErrorException  ~析构!(id: %d)\n", id);
	}

	ErrorException(const ErrorException& e) {
		id = 1;
		printf("ErrorException  拷贝构造函数!\n");
	}

	int  id;
};

//第三种情况,throw 类类型,最佳的方式是使用引用类型捕捉,抛出匿名对象
//当然,如果是动态分配的对象,直接抛出其指针
//注意:引用和普通的形参传值不能共存
int copyfile4(const char* dest, const char* src) {
	FILE* fp1 = NULL, * fp2 = NULL;

	//rb 只读方式打开一个二进制文件,只允许读取数据
	fopen_s(&fp1, src, "rb");

	if (fp1 == NULL) {
		//方式1
		//ErrorException error1;
		//throw error1;		//如果以这种方式抛出异常,下面以第一种方式接到异常,会出现throw时调用两次拷贝构造,这里还没抛出去就直接构造函数生成的error1就析构了。这里编译器会生成临时变量(匿名对象),这个变量的声明周期和局部变量error1是不一样的,catch到的也是临时变量,然后用到一次拷贝构造(上面说的),最后先析构临时变量,后析构匿名对象。
		//方式2
		throw ErrorException(); //throw ErrorException();抛出一个匿名的对象
		//方式3
		//throw new ErrorException();
	}

	//wb 以只写的方式打开或新建一个二进制文件,只允许写数据。
	fopen_s(&fp2, dest, "wb");
	if (fp2 == NULL) {
		throw - 2;
	}

	char buffer[BUFSIZE];
	int readlen, writelen;

	//如果读到数据,则大于0
	while ((readlen = fread(buffer, 1, BUFSIZE, fp1)) > 0) {
		writelen = fwrite(buffer, 1, readlen, fp2);
		if (readlen != writelen) {
			throw - 3;
		}
	}

	fclose(fp1);
	fclose(fp2);
	return 0;
}

int copyfile1(const char* dest, const char* src) {
	return copyfile4(dest, src);
}

void main() {
	int ret = 0;
	try {//保护段
		//printf("开始执行 copyfile1...\n");
		ret = copyfile1("D:/code/dest.txt", "D:/code/src.txt");
		//printf("执行 copyfile1 完毕\n");

	}
	catch (ErrorException error) {	//执行拷贝构造,见上方式1
		printf("出现异常啦!捕捉到 ErrorException 类型 id: %d\n", error.id);
	}
	//catch (ErrorException& error) {	//用引用,用方式1,就不会生成临时变量了,直接捕捉到的就是匿名对象。
	//   //error.id = 2;
	//	printf("出现异常啦!捕捉到 ErrorException &类型 id: %d\n", error.id);
	//}
	catch (ErrorException* error) {	//接到动态内存分配的对象
		printf("出现异常啦!捕捉到 ErrorException *类型 id: %d\n", error->id);
		delete error;
	}
	catch (...) {
		printf("没捉到具体的异常类型\n");
	}
	system("pause");
}

关于类,抛出异常最佳的方式是使用引用。但是具体的还是要看需求。

六、继承与异常

异常也是类,我们可以创建自己的异常类,在异常中可以使用(虚函数,派生,引用传递和数据成员等)

案例:设计一个数组类容器 Vector,重载[]操作,数组初始化时,对数组的个数进行有效检查
1) index<0 抛出异常errNegativeException
2) index = 0 抛出异常 errZeroException
3)index>1000抛出异常errTooBigException
4)index<10 抛出异常errTooSmallException
5)errSizeException类是以上类的父类,实现有参数构造、并定义virtual void printError()输出错误。

#include 

using namespace std;

class errSizeException{
public:
	errSizeException(int size){
		m_size = size;
	}

	virtual void printError(){
		cout<<"size: "<<m_size<<endl;
	}

protected:
	int m_size;
};

class errNegativeException : public errSizeException{
public:
	errNegativeException(int size):errSizeException(size){

	}

	virtual void printError(){
		cout<<"errNegativeException size: "<<m_size<<endl;
	}
};

class errZeroException : public errSizeException{
	public:
	errZeroException(int size):errSizeException(size){

	}

	virtual void printError(){
		cout<<"errZeroException size: "<<m_size<<endl;
	}
};

class errTooBigException : public errSizeException{
	public:
	errTooBigException(int size):errSizeException(size){

	}

	virtual void printError(){
		cout<<"errTooBigException size: "<<m_size<<endl;
	}
};

class errTooSmallException : public errSizeException{
	public:
	errTooSmallException(int size):errSizeException(size){

	}

	virtual void printError(){
		cout<<"errTooSmallException size: "<<m_size<<endl;
	}
};




class Vector{
public:
	Vector(int  size = 128); //构造函数

	int getLength();//获取内部储存的元素个数

	int& operator[](int index);

	~Vector();

private:
	int *m_base;
	int m_len;
};

Vector::Vector(int len){
	if(len < 0){
		throw errNegativeException(len);
	}else if(len == 0){
		throw errZeroException(len);
	}else if(len > 1000){
		throw errTooBigException(len);
	}else if(len < 10){
		throw errTooSmallException(len);
	}

	m_len = len;
	m_base = new int[len];
}

Vector::~Vector(){
	if(m_base) delete[] m_base;
	m_len = 0;
}

int Vector::getLength(){
	return m_len;
}

int &Vector::operator[](int index){
	return m_base[index];
}

void main(){

	try{
		Vector v(10000);
		for(int i=0; i<v.getLength(); i++){
			v[i] = i+10;
			printf("v[i]: %d\n", v[i]);
		}
	}catch(errSizeException &err){	//运用多态
		err.printError();
	}
	
	/*catch(errNegativeException &err){
		cout<<"errNegativeException..."<

总结:

七、异常处理的基本思想

C++的异常处理机制使得异常的引发和异常的处理不必在同一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理。上层调用者可以在适当的位置设计对不同类型异常的处理。

异常是专门针对抽象编程中的一系列错误进行处理的,C++中不能借助函数机制实现异常,因为栈结构的本质是先进后出,依次访问,无法进行跳跃,但错误处理的特征却是遇到错误信息就想要转到若干级之上进行重新尝试。所以尽量用异常处理机制。

八、标准程序库异常

C++-异常机制_第2张图片
C++-异常机制_第3张图片
C++-异常机制_第4张图片

C++-异常机制_第5张图片

#include 
#include 
#include 

using namespace std;

class Student{
public:
	Student(int age){
		if(age > 249){
			throw out_of_range("年龄太大,你是外星人嘛?");
		}
		m_age = age;
		m_space = new int[1024*1024*100];
	}

private :
	int m_age;
	int *m_space;
};


void main(){

	try{
		for(int i=1; i<1024; i++){
			Student * x = new Student(18);
		}
	}catch(out_of_range &e){
		cout<<"捕捉到一只异常:"<<e.what()<<endl;
	}catch(bad_alloc &e){
		cout<<"捕捉到动态内存分配的异常:"<<e.what()<<endl; 
	}

	system("pause");
}

你可能感兴趣的:(C++,c++,c语言,开发语言)