C++中的异常机制详解

  这两天在学习 Java,学到 Java 的异常机制时发现有些概念不理解,异常类,捕获异常的执行流程等,于是决定去复习一下 C++ 里面的异常机制,触类旁通吧,有 C++ 的基础学习 Java 语法还是很容易理解的,同样,学习 Java 也会对于 C++ 的面向对象有更深的理解,毕竟 Java 一切皆是类,下面说正题。C++ 中的异常机制,我是翻看了以前的 C++ 学习视频,西北工业大学 – C++程序设计,主讲老师魏英,个人觉得很不错的一门 C++ 学习课,很系统,由浅入深,好好看一遍保证能学到一些东西,就是那种看完以后觉得哎呀,讲的真好那种感觉,当然也可以遇到问题时用来复习查阅。

一、异常处理的基本概念

1、异常的概念

异常是指程序运行时出现的不正常。程序运行过程中可能会出现下列异常:

  • CPU 异常,例在计算过程中,出现除数为零的情况
  • 内存异常:
    使用 new 或者 malloc 申请动态内存但存储空间不够;
    数组下标越界;
    使用野指针、迷途指针读取内存
  • 设备异常:
    无法打开文件,或能够打开文件但文件有损坏,从而无法读取数据;
    正在读取磁盘文件时挪动了文件或者磁盘;
    正在使用打印机但设备被断开;
    正在使用的网络断线或阻塞
  • 用户数据异常:
    scanf 输入时数据格式错误或类型有错误
    正在处理的数据库有错误
    程序假定的数据环境发生变化

2、异常处理的方法

  • 终止模型
    在终止模型中,将假设错误非常关键,以至于程序无法返回异常发生的地方继续执行。一旦异常被抛出,就表明错误已经无法挽回,程序不能继续执行了。
  • 恢复模型
    恢复模型是指异常处理程序的工作是修正错误,然后重新尝试调动出问题的过程。

3、异常处理的机制

  抛出异常(throw)、检查异常(try)、捕获异常(catch)

  基本原理: 把需要检测的程序放到 try 块中,把异常处理的程序放到 catch 块中。如果执行一个函数时出现异常,可以不在该函数中立即处理,而是抛出异常信息,传递给它的上一级调用函数,上一级函数捕获到这个信息后在进行处理。如果上一级函数也不处理,就逐级向上传递,如果传递到了最高一级(main函数)还不处理,最后只能异常终止程序的执行。

  C++ 异常处理的机制使异常和处理可以不由同一个函数来完成,这样做的优点是使深层次的函数专注于问题求解,而不必承担处理异常的任务,减轻了深层次函数的负担,而把处理异常对的任务集中到某一层次的函数中专门来解决。

二、异常处理的实现

1、抛出异常

  可以使用throw 表达式抛出异常,将异常抛掷给主调函数去处理,异常通常以类似于实参传递给函数的方式(由 throw)抛出和(被 catch)捕获,throw 表达式的类型决定了所抛出的异常类型。由于 C++ 是根据类型来区分不同的异常的,因此在抛出异常时,throw 表达式的指没有实际意义,而表达式的类型则非常重要。如果程序中有多处要抛出异常,应该用不同的表达式类型来相互区别。

if (test == 0) throw test;	//抛出 int 型异常
if (test == 1) throw 'a';	//抛出 char 型异常
if (test == 2) throw 333.23;//抛出 double 型异常

  关于 throw 的说明

  • 执行 throw 的时候,不会执行跟在 throw 后面的语句,而是将程序从 throw 转移到匹配的 catch,该 catch 可以是同一函数中的 catch,也可以在直接或间接调用发生异常函数的上一级函数中。
  • 被抛出的对象是一个用 throw 表达式初始化的“异常对象”,异常对象将传递给对应的 catch,并在异常处理完成后撤销,因此异常对象必须是可以复制的类型(具有复制构造函数)。
  • 如果抛出的是数组,被抛出的对象自动转化为指向该数组首元素的指针,如果抛出的是一个函数,函数被转换为指向该函数的指针。
  • 如果抛出一个指针,该指针是一个指向派生类对象的基类指针,则那个对象将被分割,只抛出基类的部分。
  • 抛出指向局部对象的指针使错误的,因为在抛出指针的时候,必须确保进入异常处理程序时指针所指向的对象仍然存在。

2、检测捕获异常

检测捕获异常的一般形式为:

try
{
	...		//检测程序块(可能抛出异常的代码)
}
catch(异常说明符 1)
{
	...		//处理程序(当异常说明符 1 被抛出时执行的程序)
}
catch(异常说明符 2)
{
	...		//处理程序(当异常说明符 2 被抛出时执行的程序)
}
	...		//更多的 catch

  一个 try 块可以紧跟一个或多个 catch 块,在 try 中执行程序块所抛出的异常,通常会被其中的一个 catch 子句处理,一旦 catch 子句执行结束,程序流程继续执行紧随最后一个 catch 子句后面的语句。

  catch 子句中的异常说明符是一个有形参的形参列表,有三种形式:

catch(类型名)		//catch只需要了解异常的类型
catch(类型名 形参名)	//catch需要了解异常的类型之外的信息
catch(...)			//捕获所有异常

  因为不可能知道可能被抛出的所有异常,这时便可使用 catch(...),如果catch(...)和其它 catch 子句结合使用,那么它必须是最后一个,否则跟在它后面的 catch 子句都得不到匹配检测。

3、异常处理的执行过程

(1)程序流程到达 try 块,然后执行 try 块内的程序块,如果没有引起异常,那么跟在 try 块后的 catch 子句都不执行,程序从最后一个 catch 子句后面的语句继续执行下去;

(2)抛出异常的时候,将暂停当前函数的执行,开始查找匹配的 catch 子句;

(3)首先检查 throw 是否在 try 内部,如果是,检查 catch 子句,看是否其中之一与抛出对象相匹配,如果找到匹配的 catch,就处理异常;如果找不到,就退出当前函数并释放局部对象,然后继续在调用函数中查找;

(4)如果找到匹配的 catch,就处理异常,如果找不到,则退出调用函数,然后继续在调用这个函数的函数中查找;

(5)沿着嵌套函数调用链继续向上,直到为异常找到一个 catch 子句,只要找到能够处理异常的 catch 子句,就进入该 catch 子句,并在它的处理程序中继续执行,当 catch 结束时,跳转到该 try 块的最后一个 catch 子句之后的语句继续执行;

(6)如果始终未找到与该被抛异常匹配的 catch 子句,最终 main 函数会结束执行,则运行库函数terminate将被自动调用,terminate函数的默认功能是终止程序。

  异常处理实例:

//==========================================										   	
//	Filename : 异常处理实例							   	
//	Time     : 2019年6月26日						   
//	Author   : 柚子树					   	
//	Email    : [email protected]		   										   
//==========================================

#include <iostream>
using namespace std;

void Fun(int test)
{
	if (test == 0) throw test;	//抛出 int 型异常
	if (test == 1) throw 1.5;	//抛出 double 型异常
	if (test == 2) throw "abc";	//抛出 char* 型异常
	cout << "Fun调用正常结束!" << endl;
}

void Call_A(int test)
{
	try		//检测异常发生
	{
		Fun(test);
	}
	catch (int)
	{
		cout << "Call_A捕获异常int -> ";		//捕获异常
	}
	cout << "Call_A调用正常结束!" << endl;	//Call_A 正常结束时输出
}

void Call_B(int test)
{
	try
	{
		Call_A(test);
	}
	catch (double)
	{
		cout << "Call_B捕获异常double -> ";
	}
	catch (...)
	{
		cout << "Call_B捕获所有未知异常 -> ";
	}
	cout << "Call_B调用正常结束!" << endl;
}

int main()
{
	for (int i = 3; i >= 0; i--)
	{
		Call_B(i);
	}

	system("pause");
	return EXIT_SUCCESS;
}

  运行结果:

Fun调用正常结束!
Call_A调用正常结束!
Call_B调用正常结束!
Call_B捕获所有未知异常 -> Call_B调用正常结束!
Call_B捕获异常double -> Call_B调用正常结束!
Call_A捕获异常int -> Call_A调用正常结束!
Call_B调用正常结束!

4、重抛异常

在 catch 子句中,可以再次抛出异常,例如:

try
{
	throw "Hello";		//抛出 char* 异常
}
catch (const char*)		//捕获 char* 异常
{
	throw;				//重新抛出 char* 异常至上一级函数
}
  • 其中 throw 不加表达式,表示再次抛出 try 块中检测到的异常表达式(throw "Hello")
  • 重抛异常不能被try - catch捕获,只能传到上一级函数

5、异常接口声明

  为了加强程序的可读性,使函数的用户能够方便地知道所使用的的函数会抛掷哪些异常,可以在函数的声明中列出这个函数可能抛掷的所有异常类型。例如:

  void Fun() throw (A, B, C, D);

  这表明函数Fun()能够且只能抛掷类型 A,B,C,D 及其子类型的异常,如果在函数的声明中没有包括异常接口说明,则次函数可以抛掷任何类型的异常。例如:

  void Fun();

  一个不抛掷任何类型异常的函数可进行如下形式的声明:

  void Fun() throw ();

  注: 如果一个函数抛出了它的异常接口声明所不允许的抛出的异常时,unexpected函数会被调用,该函数的默认行为是调用terminate函数中止程序。

C++中的标准程序库异常

猜你喜欢

转载自blog.csdn.net/weixin_42482896/article/details/93685099