try
块和异常处理
在设计各种软件系统的过程中,处理程序中的错误和其他反常行为是困难的部分之一。像通信交换机和路由器这类长期运行的交互式系统必须将 90%
的程序代码用于实现错误检测和错误处理。随着基于 Web
的应用程序在运行时不确定性的增多,越来越多的程序员更加注重错误的处理。
异常就是运行时出现的不正常,例如运行时耗尽了内存或遇到意外的非法输入。异常存在于程序的正常功能之外,并要求程序立即处理。
在设计良好的系统中,异常是程序错误处理的一部分。当程序代码检查到无法处理的问题时,异常处理就特别有用。在这些情况下,检测出问题的那部分程序需要一种方法把控制权转到可以处理这个问题的那部分程序。错误检测程序还必须指出具体出现了什么问题,并且可能需要提供一些附加信息。
异常机制提供程序中错误检测与错误处理部分之间的通信。C++
的异常处理中包括:
1. throw
表达式,错误检测部分使用这种表达式来说明遇到了不可处理的错误。可以说,throw
引发了异常条件。
2. try
块,错误处理部分使用它来处理异常。try
语句块以 try
关键字开始,并以一个或多个 catch
子句结束。在 try
块中执行的代码所抛出(throw
)的异常,通常会被其中一个 catch
子句处理。由于它们“处理”异常,catch
子句也称为处理代码。
3.
由标准库定义的一组 异常类,用来在 throw
和相应的 catch
之间传递有关的错误信息。
在本节接下来的部分将要介绍这三种异常处理的构成。
throw
表达式
系统通过 throw
表达式抛出异常。throw
表达式由关键字 throw
以及尾随的表达式组成,通常以分号结束,这样它就成为了表达式语句。throw
表达式的类型决定了所抛出异常的类型。
回顾第 1.5.2
节将两个 Sales_item
类型对象相加的程序,就是一个简单的例子。该程序检查读入的记录是否来自同一本书。如果不是,就输出一条信息然后退出程序。
Sales_item item1, item2;
std::cin >> item1 >> item2;
// first check that item1 and item2 represent the same book
if (item1.same_isbn(item2)) {
std::cout << item1 + item2 << std::endl;
return 0; // indicate success
} else {
std::cerr << "Data must refer to same ISBN"
295
<< std::endl;
return -1; // indicate failure
}
在使用 Sales_items
的更简单的程序中,把将对象相加的部分和负责跟用户交互的部分分开。在这个例子中,用 throw
抛出异常来改写检测代码:
// first check that data is for the same item
if (!item1.same_isbn(item2))
throw runtime_error("Data must refer to same ISBN");
// ok, if we're still here the ISBNs are the same
std::cout << item1 + item2 << std::endl;
这段代码检查 ISBN
对象是否不相同。如果不同的话,停止程序的执行,并将控制转移给处理这种错误的处理代码。
throw
语句使用了一个表达式。在本例中,该表达式是 runtime_error
类型的对象。runtime_error
类型是标准库异常类中的一种,在 stdexcept
头文件中定义。在后续章节中很快就会更详细地介绍这些类型。我们通过传递 string
对象来创建 runtime_error
对象,这样就可以提供更多关于所出现问题的相关信息。
try
块
try
块的通用语法形式是:
try {
program-statements
} catch (exception-specifier) {
handler-statements
} catch (exception-specifier) {
handler-statements
} //...
try
块以关键字 try
开始,后面是用花括号起来的语句序列块。try
块后面是一个或多个 catch
子句。每个 catch
子句包括三部分:关键字 catch
,圆括号内单个类型或者单个对象的声明——称为异常说明符,以及通常用花括号括起来的语句块。如果选择了一个 catch
子句来处理异常,则执行相关的块语句。一旦 catch
子句执行结束,程序流程立即继续执行紧随着最后一个 catch
子句的语句。
try
语句内的 program-statements
形成程序的正常逻辑。这里面可以包含任意 C++
语句,包括变量声明。与其他块语句一样,try
块引入局部作用域,在 try
块中声明的变量,包括 catch
子句声明的变量,不能在 try
外面引用。
编写处理代码
在前面的例子中,使用了 throw
来避免将两个表示不同书的 Sales_items
对象相加。想象一下将 Sales_items
对象相加的那部分程序与负责与用户交流的那部分是分开的,则与用户交互的部分也许会包含下面的用于处理所捕获异常的代码:
while (cin >> item1 >> item2) {
try {
// execute code that will add the two Sales_items
// if the addition fails, the code throws a runtime_error
exception
} catch (runtime_error err) {
// remind the user that ISBN must match and prompt for
another pair
cout << err.what()
<< "\nTry Again? Enter y or n" << endl;
char c;
cin >> c;
if (cin && c == 'n')
break; // break out of the while loop
}
}
关键字 try
后面是一个块语句。这个块语句调用处理 Sales_item
对象的程序部分。这部分也可能会抛出 runtime_error
类型的异常。上述 try
块提供单个 catch
子句,用来处理 runtime_error
类型的异常。在执行 try
块代码的过程中,如果在 try
块中的代码抛出 runtime_error
类型的异常,则处理这类异常的动作在 catch
后面的块语句中定义。本例中,catch
输出信息并且询问用户是否继续进行异常处理。如果用户输入'n'
,则结束while
;否则继续循环,读入两个新的 Sales_items
对象。通过输出 err.what()
的返回值提示用户。大家都知道 err
返回runtime_error
类型的值,因此可以推断出 what
是runtime_error
类的一个成员函数(1.5.2
节)。每一个标准库异常类都定义了名为 what
的成员函数。这个函数不需要参数,返回 C
风格字符串。在出现 runtime_error
的情况下,what
返回的 C
风格字符串,是用于初始化runtime_error
的 string
对象的副本。如果在前面章节描述的代码抛出异常,那么执行这个 catch
将输出。
Data must refer to same ISBN
Try Again? Enter y or n
函数在寻找处理代码的过程中退出
在复杂的系统中,程序的执行路径也许在遇到抛出异常的代码之前,就已经经过了多个 try
块。例如,一个 try
块可能调用了包含另一 try
块的函数,它的 try
块又调用了含有 try
块的另一个函数,如此类推。
寻找处理代码的过程与函数调用链刚好相反。抛出一个异常时,首先要搜索的是抛出异常的函数。如果没有找到匹配的 catch
,则终止这个函数的执行,并在调用这个函数的函数中寻找相配的 catch
。如果仍然找到相应的处理代码,该函数同样要终止,搜索调用它的函数。如此类推,继续按执行路径回退,直到找到适当类型的 catch
为止。
如果不存在处理该异常的 catch
子句,程序的运行就要跳转到名为terminate
的标准库函数,该函数在 exception
头文件中定义。这个标准库函数的行为依赖于系统,通常情况下,它的执行将导致程序非正常退出。
在程序中出现的异常,如果没有经 try
块定义,则都以相同的方式来处理:毕竟,如果没有任何 try
块,也就没有捕获异常的处理代码(catch
子句)。此时,如果发生了异常,系统将自动调用 terminate
终止程序的执行。
标准异常
C++
标准库定义了一组类,用于报告在标准库中的函数遇到的问题。程序员可在自己编写的程序中使用这些标准异常类。标准库异常类定义在四个头文件中:
1. exception
头文件定义了最常见的异常类,它的类名是 exception
。这个类只通知异常的产生,但不会提供更多的信息。
2. stdexcept
头文件定义了几种常见的异常类,这些类型在表 6.1
中列出。
表 6.1
在<stdexcept>
头文件中定义的标准异常类
new
头文件定义了 bad_alloc
异常类型,提供因无法分配内在而由 new
(第 5.11
节)抛出的异常。
3. type_info
头文件定义了 bad_cast
异常类型,这种类型将第 18.2
节讨论。
标准库异常类
标准库异常类只提供很少的操作,包括创建、复制异常类型对象以及异常类型对象的赋值。 exception
、bad_alloc
以及 bad_cast
类型只定义了默认构造函数(第 2.3.4
节),无法在创建这些类型的对象时为它们提供初值。其他的异常类型则只定义了一个使用 string
初始化式的构造函数。当需要定义这些异常类型的对象时,必须提供一想 string
参数。string
初始化式用于为所发生的错误提供更多的信息。
异常类型只定义了一个名为 what
的操作。这个函数不需要任何参数,并且返回 const char*
类型值。它返回的指针指向一个 C
风格字符串(第 4.3
节)。使用 C
风格字符串的目的是为所抛出的异常提出更详细的文字描述。
what
函数所返回的指针指向 C
风格字符数组的内容,这个数组的内容依赖于异常对象的类型。对于接受 string
初始化式的异常类型,what
函数将返回该 string
作为 C
风格字符数组。对于其他异常类型,返回的值则根据编译器的变化而不同。
使用预处理器进行调试
第 2.9.2
节介绍了如何使用预处理变量来避免重复包含头文件。C++
程序员有时也会使用类似的技术有条件地执行用于调试的代码。这种想法是:程序所包含的调试代码仅在开发过程中执行。当应用程序已经完成,并且准备提交时,就会将调试代码关闭。可使用 NDEBUG
预处理变量实现有条件的调试代码:
int main()
{
#ifndef NDEBUG
cerr << "starting main" << endl;
#endif
// ...
如果 NDEBUG
未定义,那么程序就会将信息写到 cerr
中。如果 NDEBUG
已经定义了,那么程序执行时将会跳过 #ifndef
和 #endif
之间的代码。
默认情况下,NDEBUG
未定义,这也就意味着必须执行 #ifndef
和 #endif
之间的代码。在开发程序的过程中,只要保持 NDEBUG
未定义就会执行其中的调试语句。开发完成后,要将程序交付给客户时,可通过定义 NDEBUG
预处理变量,(有效地)删除这些调试语句。大多数的编译器都提供定义 NDEBUG
命令行选项:
$ CC -DNDEBUG main.C
这样的命令行行将于在 main.c
的开头提供 #define NDEBUG
预处理命令。
预处理器还定义了其余四种在调试时非常有用的常量:
__FILE__
文件名
__LINE__
当前行号
__TIME__
文件被编译的时间
__DATE__
文件被编译的日期
可使用这些常量在错误消息中提供更多的信息:
if (word.size() < threshold)
cerr << "Error: " << _ _FILE_ _
<< " : line " << _ _LINE_ _ << endl
<< " Compiled on " << _ _DATE_ _
<< " at " << _ _TIME_ _ << endl
<< " Word read was " << word
<< ": Length too short" << endl;
如果给这个程序提供一个比 threshold
短的 string
对象,则会产生下面
的错误信息:
Error: wdebug.cc : line 21
Compiled on Jan 12 2005 at 19:44:40
Word read was "foo": Length too short
另一个常见的调试技术是使用 NDEBUG
预处理变量以及 assert
预处理宏。assert
宏是在 cassert
头文件中定义的,所有使用 assert
的文件都必须包含这个头文件。预处理宏有点像函数调用。assert
宏需要一个表达式作为它的条件:
assert(expr)
只要 NDEBUG
未定义,assert
宏就求解条件表达式 expr
,如果结果为false
,assert
输出信息并且终止程序的执行。如果该表达式有一个非零(例如,true
)值,则 assert
不做任何操作。与异常不同(异常用于处理程序执行时预期要发生的错误),程序员使用assert
来测试“不可能发生”的条件。例如,对于处理输入文本的程序,可以预测全部给出的单词都比指定的阈值长。那么程序可以包含这样一个语句:
assert(word.size() > threshold);
在测试过程中,assert
等效于检验数据是否总是具有预期的大小。一旦开发和测试工作完成,程序就已经建立好,并且定义了 NDEBUG
。在成品代码中,assert
语句不做任何工作,因此也没有任何运行时代价。当然,也不会引起任何运行时检查。assert
仅用于检查确实不可能的条件,这只对程序的调试有帮助,但不能用来代替运行时的逻辑检查,也不能代替对程序可能产生的错误的检测。
分享到:
相关推荐
C#异常处理总结及简单实例 一、异常处理的理解? 异常处理是指程序在运行过程中,发生错误会导致程序退出,这种错误,就叫做异常。 因此处理这种错误,就称为异常处理。 二、异常处理如何操作? C# 异常处理时建立在...
MySQL中的异常处理是数据库编程中不可或缺的一部分,它允许开发者预设对可能出现的错误或异常的响应,从而确保程序的稳定性和健壮性。在MySQL中,异常定义和处理主要是通过`DECLARE`语句来实现的。 1. **异常定义**...
异常处理是编程中的一种机制,用于捕获和处理运行时发生的错误或异常情况。异常可以由硬件引发,如硬件异常,也可以由操作系统或应用程序自身触发,即软件异常。当异常发生时,操作系统允许程序有机会检查异常类型并...
在易语言中,线程是并发执行的程序单位,线程结构异常处理是编程过程中非常重要的一环,因为线程可能会遇到各种异常情况,如内存访问错误、除零异常等。 线程结构异常处理源码是易语言中处理这些异常的关键部分。当...
Java 异常处理习题 Java 异常处理是 Java 编程语言中的一种重要机制,用于处理程序在运行时可能出现的错误或异常情况。下面是关于 Java 异常处理的习题和知识点总结: 一、Java 异常处理关键字 * Java 中用来抛出...
"New_SE_Handler"可能是一个新的结构化异常处理程序,结构化异常处理(SEH)是Windows操作系统中的一个特性,用于处理硬件和软件异常。 "GetSeAddr"可能是获取异常发生时的地址函数,这对于分析异常原因和定位问题...
在编程领域,异常处理是确保程序健壮性与稳定性的关键技术。对于C、C++以及基于MFC(Microsoft Foundation Classes)的开发来说,异常处理更是不可或缺的一部分。本篇文章将深入浅析C、C++中的异常处理机制以及MFC中...
详解 SpringCloud Finchley Gateway 统一异常处理 SpringCloud Finchley Gateway 统一异常处理是指在使用 SpringCloud Finchley 版本的 Gateway 时,如何统一处理系统级异常的方法。默认情况下,SpringCloud ...
【异常处理】是编程中必不可少的一个环节,尤其是在Java这样的面向对象语言中。异常处理机制使得程序在遇到错误时能够优雅地中断执行流程,提供错误信息,并有机会进行恢复操作,而不是简单地崩溃。以下是对异常处理...
异常处理是Java编程中至关重要的一个概念,它确保了程序在遇到错误或异常情况时能够以优雅的方式继续执行或者终止。下面是对Java异常处理机制的详细解析。 在Java中,异常是程序运行时发生的错误,它中断了正常的...
ARM处理器异常处理是指ARM微处理器对各种异常情况作出响应和处理的过程。异常指的是处理器在正常执行程序时遇到的特殊情况,例如外部中断请求、未对齐的内存访问错误、指令预取终止等。为了保证系统的稳定性和正确性...
在 PowerBuilder 9(简称 PB9)开发过程中,异常处理是一项关键的技术,它能帮助开发者有效地管理和解决程序中出现的错误,确保系统的稳定运行。在实际应用中,开发者经常会遇到各种预知和不可预知的问题,如系统级...
游标和异常处理 游标是 SQL 的一个内存工作区,由系统或用户以变量的形式定义。游标的作用就是用于临时存储从数据库中提取的数据块。在某些情况下,需要把数据从存放在磁盘的表中调到计算机内存中进行处理,最后将...
《ADS异常处理详解》 异常处理是嵌入式系统中至关重要的部分,特别是在基于ARM架构的系统中。本文将深入探讨ARM处理器的异常处理机制,包括异常类型、处理流程、异常优先级以及向量表等内容。 一、异常类型 ARM...
Java异常处理是编程中至关重要的一个环节,它确保了程序在遇到错误时能够优雅地运行,而不是突然崩溃。本实验报告“java实验报告4-异常处理”旨在帮助初学者掌握Java中的异常处理机制,以及如何利用log4j进行日志...
React Native 异常处理库是专门为在React Native框架下开发的混合移动应用提供错误管理和调试支持的工具。React Native允许开发者使用JavaScript编写原生移动应用,但JavaScript代码的运行环境中可能会遇到各种错误...
### Spring Cloud Gateway全局异常处理详解 #### 一、引言 在微服务架构中,网关作为服务入口,承担着路由转发、限流熔断、鉴权认证等职责。Spring Cloud Gateway作为一款基于Spring Framework 5、Project Reactor...
课程作业,实现两数计算及其异常处理,异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。 Java中的异常可以是函数...