当前课程知识点:基于Linux的C++ >  第十一讲 泛型编程 >  11.4 异常处理机制(二) >  LinuxCPP1104

返回《基于Linux的C++》慕课在线视频课程列表

LinuxCPP1104在线视频

LinuxCPP1104

下一节:LinuxCPP1105

返回《基于Linux的C++》慕课在线视频列表

LinuxCPP1104课程教案、知识点、字幕

刚才那个例子

因为我们定义的是两个空异常类

它所能够提供的信息是有限的

我们可以为这样的两个异常类

提供更丰富的信息

在实际的编程中 所有的这些异常

我们都应该精心的设计对应的异常类

能够提供足够的完整的信息

这样在异常处理的时候

它的处理的方案和处理的方法

才能够更加灵活

对于我们的 EStackFull 这个类来说

我们提供一个值这样的一个私有字段

然后在引发这个异常的时候

我们能够提供这个值的信息

就是说你当时

在把哪一个数据压栈的时候出了异常

那么我们这个 _value 就保存那个数据

它可以提供更详尽的信息

那么 在 Push() 这个成员函数里边

当栈是满的时候

它就会 throw EStackFull( value )

它就用压栈的这个值

构造这个异常类的一个对象

然后把这个对象抛出

这一点在异常编程的时候是特别重要的

主程序当然也需要做一些修改

try 块还一样 我们尝试着压栈

catch 想捕获这个异常

我们捕获的是哪一个异常呢

当然是 EStackFull 类的一个引用

OK 我们就写 const EStackFull & e

我们就捕获这个异常

那个异常对象在 Push() 的过程中

不是被抛出来了吗

那么我们的 catch 就要捕获那个异常

Push() 所构造的那个异常对象

在 catch 这个子句里

将通过 e 这个引用来访问

所以我们就可以在 catch 这个子句里边

通过 e 来获取那个异常对象的特定的属性信息

通过 e 来获取那个异常对象的特定的属性信息

它到底是在压入哪一个数据的时候引发这个异常的

它到底是在压入哪一个数据的时候引发这个异常的

那么 e.GetValue() 一下子就能够获得那个值

这一点显然比刚才空类的设计要好得多

有一点是需要特别说明的

异常类本身可以派生和继承

你在 C++ 的类库架构下边写一个异常类

再写一个异常类

互相之间完全没关系

那种可能性其实是不多的

我们可以为所有的这些异常类本身

也构造一个完整的类的继承层次

正常情况下 我们的程序逻辑应该是

有一个顶层的抽象的异常类

然后后续的所有的异常类

都应该从这个异常类继承下来

事实上 C++ 标准库已经给我们

提供了这样的一个异常类

而你写的异常类应该都从 C++ 标准库的

那个异常类里边继承下来

可捕获的异常对象的型式

有一点是需要说明的

在 catch 那个子句里边

我们可以捕获任何一个普通型式 包括类

整型可以 浮点型可以

字符串可以 类也可以

但是你要注意

当你以普通型式的形式来捕获的时候

那么这些异常对象就需要拷贝

你也可以捕获对某型式对象的一个引用

这个时候是没有额外拷贝动作的

因为你传递的时候

它那个参数将是以引用的方式进行传递

是没有额外的拷贝动作的 这是第二个

第三个 你也可以捕获指向某型式对象的一个指针 这也可以

第三个 你也可以捕获指向某型式对象的一个指针 这也可以

但是你要记住 在这种情况下

它会要求对象动态构造

这样的话 那个指针在 catch 里才可以访问到

不管怎么样 你必须保证那个对象

在我们的 catch 子句里可以访问

所以如果是指向某型式对象的指针

那么这样一个对象在大部分情况下

都应该是动态构造的

在 catch 子句里

可以封装对这个特定异常的一些必要的处理代码

可以封装对这个特定异常的一些必要的处理代码

实际编写程序的时候

你的 try ... catch 块

那个 catch 块我们称它为 catch 子句

实际上可以有很多个

每一个 catch 子句都负责捕获一种、一类

或者全部异常

如果你按照这样的模式写

catch( int )、catch( const char * )

那么它将捕获这一种异常

catch( int ) 只能捕获整数异常

catch( const char * ) 只能捕获 const char *

这种型式的异常

它不能捕获其它的异常

如果你是按照这样的方式写

catch( const EStackFull & )

有没有“&”都一样

它就只能捕获一类异常

为什么是一类呢

是因为所有的 EStackFull 这一类的对象

它都可以捕获

另外 EStackFull 这个类的所有派生类

它们的异常也可以被捕获

也就是说 你可以通过基类

捕获派生类的异常

正是在这个角度来讲

我们说 catch( const EStackFull & )

像这样的异常捕获

它捕获的是一类异常而不是一种

第三个 你如果写 catch(...) 中间省略了

就表示所有类型的异常它都可以捕获

在执行的时候 所有的 catch 子句

是按照你定义的顺序去做的

所以 如果你的异常类是有继承层次的

那么你必须把派生类的那个异常

对应的 catch 子句写在前边

把基类的那个 catch 子句写在后边

否则的话 你的派生类那个异常

是没有机会得到执行的

就像我们刚才讲的

因为你通过 catch(后面是一个类)

或者是一个类的一个引用

或者是一个指向这个类的指针的时候

你如果按照这样模式进行定义

不管它是那个类本身

还是那个类的引用

还是指向那个类的指针

这个参数的型式它都能够捕获

那个类和它所有的派生类异常

所以在这种情况下 千万要记得

如果有基类和派生类

应该把派生类写在前边 然后才能写基类的

应该把派生类写在前边 然后才能写基类的

可以在基本任务完成后

重新引发所处理的异常

你在处理异常的过程中

完成一个基本的任务

然后为了保证后续的代码

依然还能够处理这样的异常

或者你认为在这个程序处理完之后

这个异常有必要重新地被再次处理

我们说再引发

异常的再引发主要用于

在程序终止的时候写入我们的日志

在程序终止之前

我要完成这个日志的编写工作

把这个情况、这个信息记录在案

那么这个时候

我们就可以把它放在我们 catch 子句里

处理完了以后

那我们的程序流程就应该停止

然后可能还要做后续的异常

比如说完成我们的数据对象清除工作

那么在这种情况下面

这个异常就可以重新地再引发

去实施特定的清除任务

也有可能我们需要这一点

所以异常的再引发主要用于

在程序终止前写入我们的日志

和进行特殊的清除工作

异常再引发的方式特别简单

就是 throw 这个关键字

后边什么东西都不用带了

catch(...) 全部异常都处理

然后我们 throw

处理完以后我重新引发这个异常

那么更顶层的 catch 逻辑

就可以紧接着去处理它对应的异常

根据它的异常类具体的信息

来去做特定的处理

而我们这里不管三七二十一

先把它比如说

完成我们日志写入工作再说

写完了我再重新引发那个异常

这个处理方式是合乎逻辑的

在异常处理的过程中

我们的编译器会完成一个栈的展开

这一点在编程的时候

实际上就已经写在了我们的可执行代码里

异常引发的代码和异常处理代码

可能是位于不同函数的

这一点同学们一定要清楚

所以当异常发生的时候

它会沿着这个异常处理块的

嵌套顺序逆向地查找

去找它对应的 catch 子句

找着了那个 catch 子句

它就会执行 catch 子句里边的那段代码

来处理我们这个异常

异常如果处理完了

那么程序就会保持在这个 catch 子句

所在的那个函数的栈框架里边

不会返回引发异常的那个函数栈框架

特别注意这一点

为什么我们说在异常处理的时候

要进行栈展开呢

就是因为这个道理

因为你异常的引发代码和异常处理代码

不在一个函数里

当它向后回溯的时候

来找你对应的 catch 子句

来处理这个异常的时候

它不得不把引发那个异常的栈框架给去掉

如果它没找到的话

向上回溯一级函数调用

看主调函数里有没有对应的 catch 子句

如果没有 它还要继续向上回溯

再取消一次函数栈框架

一直到找到这个 catch 子句为止

然后完成这个异常处理的流程

这个流程一做完

它还能回到引发这个异常的

那个函数调用栈框架里边吗

它恢复不过去了

所以整个程序流程

这个时候就会停留在这个 catch 子句

栈框架里边

并且继续往下去做 它回不到原点了

这是非常重要的一个地方

函数栈框架消失了

编译器本身能够替我们

自动地将局部对象析构 它会清除

但是如果你动态分配了一段数据对象

你动态分配了一段内存

那么在catch子句里边

如果你没有调用 delete 操作符去析构它

那么动态分配的内存 它就会保留原样

也就是说 你动态分配的

这些目标数据对象就不会被析构

这一点在编写异常处理代码的时候

是需要特别注意的

这不就导致内存泄露了吗

所以一定要非常小心

所有的未处理异常

都由预定义的 std::terminate() 这个函数来处理

你可以使用 std::set_terminate() 这个函数

来设置 terminate() 函数的处理例程

我们看这段代码 这个示例里边

我们可以定义一个 term_func()

这是一个终止函数

那么你可以在 try 块里边

调用 set_terminate() 这个函数

将我们这个 term_func 传递给它

也就是说 terminate() 这个函数

在异常没有处理情况下面

调用我们的 term_func() 这个函数

去做特定的处理 处理完了

你也可以继续引发一个异常 这没关系

set_terminate() 将会将我们的异常处理例程

设置为我们的 term_func

然后你引发这个异常

引发这个异常之后

因为后面的 catch 子句只处理整数异常

它并不处理我们的字符串异常

所以这个异常将没有被获得处理

因为是未处理的

terminate() 这个函数就会处理这个异常

它怎么处理呢 当然是调用我们

term_func() 这个函数对它进行处理

这就是未处理异常最后一次处理的时机

就在这里 如果你没设

那么所有未处理的异常

都将交给操作系统来处理了

如果要描述函数是否引发异常

一个是 C++11 之前的逻辑

就是使用 throw 这个关键字

throw() 表示这样的一个函数是不引发异常

如果说这样一个函数

可能引发任意型式的异常

那么你就写 throw( ... )

如果这样的函数

只引发某种特定类型的异常

那么你就把那个特定类型

写在 throw 的小括号对里边

注意 有些编译器 当你写 throw( T ) 的时候

它自动地把它理解为throw( ... )

也就是说 它不关心小括号对里面的那个T

到底是什么型

在 C++11 规范里边

如果你要描述一个函数

不会引发任何异常

那么你就应该写新的关键字 noexcept

它等价于 noexcept( true )

如果这样一个函数确实引发一个异常

那么你就写 noexcept( false )

如果它可能引发异常

也就说可能引发 可能不引发

那么你就应该写 noexcept( noexcept( expr ) )

看上去很怪 expr 是什么呢

expr 就是可转换为 true 和 false 的

一个常数表达式

这两个 noexcept 不能省略了

第一个 noexcept 其实是关键字

来描述这个异常处理策略

也就是异常描述规范的

第二个 noexcept 其实是一个操作符

它是计算那个 expr

把它的结果变成 true 和 false

所以这两个 noexcept 都不能省略

在 C++11 下边

建议你使用 noexcept 来取代 throw

它认为 throw 已经过时了

你不应该用了 就是这个意思

不管你是用 throw 还是用 noexcept

来描述一个函数是否引发异常

这个东西 就叫异常描述规范

对于我们这个栈类

因为 Pop() 和 Push() 都有可能导致异常

所以我们在编写成员函数原型的时候

就应该描述清楚

这两个函数是否引发异常

引发的是什么类型的异常

应该写在这个函数的声明处

这个就叫异常描述规范

你看我是这些写的

int Pop() throw( EStackEmpty )

void Push() throw( EStackFull )

就表示这两个函数

将会引发这两种类型的异常

如果你是在 C++11 下边写了

你当然你应该写 noexcept( false )

不是不引发异常 对吧

那就是会引发异常的

基于Linux的C++课程列表:

第一讲 C/C++基本语法元素

-1.1 提纲

--LinuxCPP0101

-1.2 程序设计的基本概念

--LinuxCPP0102

-1.3 简单C/C++程序介绍

--LinuxCPP0103

-1.4 程序设计的基本流程

--LinuxCPP0104

-1.5 基本语法元素

--LinuxCPP0105

-1.6 程序设计风格

--LinuxCPP0106

-1.7 编程实践

--LinuxCPP0107

-第一讲 C/C++基本语法元素--编程实践提交入口

第二讲 程序控制结构

-2.1 提纲

--LinuxCPP0201

-2.2 结构化程序设计基础

--LinuxCPP0202

-2.3 布尔数据

--LinuxCPP0203

-2.4 分支结构

--LinuxCPP0204

-2.5 break语句

--LinuxCPP0205

-2.6 循环结构

--LinuxCPP0206

-2.7 编程实践

--LinuxCPP0207

-第二讲 程序控制结构--编程实践提交入口

第三讲 函数

-3.1 提纲

--LinuxCPP0301

-3.2 函数声明、调用与定义

--LinuxCPP0302

-3.3 函数调用栈框架

--LinuxCPP0303

-3.4 编程实践

--LinuxCPP0304

-第三讲 函数--编程实践提交入口

第四讲 算法

-4.1 提纲

--LinuxCPP0401

-4.2 算法概念与特征

--LinuxCPP0402

-4.3 算法描述

--LinuxCPP0403

-4.4 算法设计与实现

--LinuxCPP0404

-4.5 递归算法(一)

--LinuxCPP0405

-4.6 递归算法(二)

--LinuxCPP0406

-4.7 容错与计算复杂度

--LinuxCPP0407

-4.8 编程实践

--LinuxCPP0408

-第四讲 算法--编程实践提交入口

第五讲 程序组织与开发方法

-5.1 提纲

--LinuxCPP0501

-5.2 库与接口

--LinuxCPP0502

-5.3 随机数库(一)

--LinuxCPP0503

-5.4 随机数库(二)

--LinuxCPP0504

-5.5 作用域与生存期

--LinuxCPP0505

-5.6 典型软件开发流程(一)

--LinuxCPP0506

-5.7 典型软件开发流程(二)

--LinuxCPP0507

-5.8 编程实践

--LinuxCPP0508

-第五讲 程序组织与开发方法--编程实践提交入口

第六讲 复合数据类型

-6.1 提纲

--LinuxCPP0601

-6.2 字符

--LinuxCPP0602

-6.3 数组(一)

--LinuxCPP0603

-6.4 数组(二)

--LinuxCPP0604

-6.5 结构体

--LinuxCPP0605

-6.6 编程实践

--LinuxCPP0606

-第六讲 复合数据类型--编程实践提交入口

第七讲 指针与引用

-7.1 提纲

--LinuxCPP0701

-7.2 指针基本概念

--LinuxCPP0702

-7.3 指针与函数

--LinuxCPP0703

-7.4 指针与复合数据类型(一)

--LinuxCPP0704

-7.5 指针与复合数据类型(二)

--LinuxCPP0705

-7.6 字符串

--LinuxCPP0706

-7.7 动态存储管理(一)

--LinuxCPP0707

-7.8 动态存储管理(二)

--LinuxCPP0708

-7.9 引用

--LinuxCPP0709

-7.10 编程实践

--LinuxCPP0710

-第七讲 指针与引用--编程实践提交入口

第八讲 链表与程序抽象

-8.1 提纲

--LinuxCPP0801

-8.2 数据抽象(一)

--LinuxCPP0802

-8.3 数据抽象(二)

--LinuxCPP0803

-8.4 链表(一)

--LinuxCPP0804

-8.5 链表(二)

--LinuxCPP0805

-8.6 链表(三)

--LinuxCPP0806

-8.7 链表(四)

--LinuxCPP0807

-8.8 函数指针(一)

--LinuxCPP0808

-8.9 函数指针(二)

--LinuxCPP0809

-8.10 抽象链表(一)

--LinuxCPP0810

-8.11 抽象链表(二)

--LinuxCPP0811

-8.12 编程实践

--LinuxCPP0812

-第八讲 链表与程序抽象--编程实践提交入口

第九讲 类与对象

-9.1 提纲

--LinuxCPP0901

-9.2 程序抽象与面向对象

--LinuxCPP0902

-9.3 类类型

--LinuxCPP0903

-9.4 对象(一)

--LinuxCPP0904

-9.5 对象(二)

--LinuxCPP0905

-9.6 类与对象的成员(一)

--LinuxCPP0906

-9.7 类与对象的成员(二)

--LinuxCPP0907

-9.8 类与对象的成员(三)

--LinuxCPP0908

-9.9 继承(一)

--LinuxCPP0909

-9.10 继承(二)

--LinuxCPP0910

-9.11 继承(三)

--LinuxCPP0911

-9.12 多态(一)

--LinuxCPP0912

-9.13 多态(二)

--LinuxCPP0913

-9.14 编程实践

--LinuxCPP0914

-第九讲 类与对象--编程实践提交入口

第十讲 操作符重载

-10.1 提纲

--LinuxCPP1001

-10.2 四则运算符重载(一)

--LinuxCPP1002

-10.3 四则运算符重载(二)

--LinuxCPP1003

-10.4 关系与下标操作符重载

--LinuxCPP1004

-10.5 赋值操作符重载(一)

--LinuxCPP1005

-10.6 赋值操作符重载(二)

--LinuxCPP1006

-10.7 赋值操作符重载(三)

--LinuxCPP1007

-10.8 赋值操作符重载(四)

--LinuxCPP1008

-10.9 赋值操作符重载(五)

--LinuxCPP1009

-10.10 流操作符重载(一)

--LinuxCPP1010

-10.11 流操作符重载(二)

--LinuxCPP1011

-10.12 流操作符重载(三)

--LinuxCPP1012

-10.13 操作符重载总结

--LinuxCPP1013

-10.14 编程实践

--LinuxCPP1014

-第十讲 操作符重载--编程实践提交入口

第十一讲 泛型编程

-11.1 提纲

--LinuxCPP1101

-11.2 泛型编程概览

--LinuxCPP1102

-11.3 异常处理机制(一)

--LinuxCPP1103

-11.4 异常处理机制(二)

--LinuxCPP1104

-11.5 运行期型式信息(一)

--LinuxCPP1105

-11.6 运行期型式信息(二)

--LinuxCPP1106

-11.7 模板与型式参数化

--LinuxCPP1107

-11.8 题外话:术语翻译

--LinuxCPP1108

-11.9 泛型编程实践(一)

--LinuxCPP1109

-11.10 泛型编程实践(二)

--LinuxCPP1110

-11.11 泛型编程实践(三)

--LinuxCPP1111

-11.12 泛型编程实践(四)

--LinuxCPP1112

-11.13 泛型编程实践(五)

--LinuxCPP1113

-11.14 泛型编程实践(六)

--LinuxCPP1114

-11.15 泛型编程实践(七)

--LinuxCPP1115

-11.16 泛型编程实践(八)

--LinuxCPP1116

-11.17 泛型编程实践(九)

--LinuxCPP1117

-11.18 泛型编程实践(十)

--LinuxCPP1118

-11.19 编程实践

--LinuxCPP1119

-第十一讲 泛型编程--编程实践提交入口

第十二讲 Linux系统编程基础

-12.1 提纲

--LinuxCPP1201

-12.2 程序执行环境(一)

--LinuxCPP1202

-12.3 程序执行环境(二)

--LinuxCPP1203

-12.4 程序执行环境(三)

--LinuxCPP1204

-12.5 程序执行环境(四)

--LinuxCPP1205

-12.6 输入输出(一)

--LinuxCPP1206

-12.7 输入输出(二)

--LinuxCPP1207

-12.8 文件系统

--LinuxCPP1208

-12.9 设备

--LinuxCPP1209

-12.10 库(一)

--LinuxCPP1210

-12.11 库(二)

--LinuxCPP1211

-12.12 makefile文件(一)

--LinuxCPP1212

-12.13 makefile文件(二)

--LinuxCPP1213

-12.14 makefile文件(三)

--LinuxCPP1214

-12.15 编程实践

--LinuxCPP1215

-第十二讲 Linux系统编程基础--编程实践提交入口

第十三讲 进程编程

-13.01 提纲

--LinuxCPP1301

-13.02 进程基本概念

--LinuxCPP1302

-13.03 信号

--LinuxCPP1303

-13.04 进程管理(一)

--LinuxCPP1304

-13.05 进程管理(二)

--LinuxCPP1305

-13.06 进程管理(三)

--LinuxCPP1306

-13.07 进程间通信(一)

--LinuxCPP1307

-13.08 进程间通信(二)

--LinuxCPP1308

-13.09 进程间通信(三)

--LinuxCPP1309

-13.10 进程间通信(四)

--LinuxCPP1310

-13.11 进程池

--LinuxCPP1311

-13.12 编程实践

--LinuxCPP1312

-第十三讲 进程编程--编程实践提交入口

第十四讲 线程编程

-14.1 提纲

--LinuxCPP1401

-14.2 线程基本概念

--LinuxCPP1402

-14.3 线程管理(一)

--LinuxCPP1403

-14.4 线程管理(二)

--LinuxCPP1404

-14.5 线程管理(三)

--LinuxCPP1405

-14.6 线程管理(四)

--LinuxCPP1406

-14.7 线程同步机制(一)

--LinuxCPP1407

-14.8 线程同步机制(二)

--LinuxCPP1408

-14.9 C++11线程库(一)

--LinuxCPP1409

-14.10 C++11线程库(二)

--LinuxCPP1410

-14.11 C++11线程库(三)

--LinuxCPP1411

-14.12 C++11线程库(四)

--LinuxCPP1412

-14.13 C++11线程库(五)

--LinuxCPP1413

-14.14 编程实践

--LinuxCPP1414

-第十四讲 线程编程--编程实践提交入口

第十五讲 网络编程

-15.1 提纲

--LinuxCPP1501

-15.2 Internet网络协议

--LinuxCPP1502

-15.3 套接字(一)

--LinuxCPP1503

-15.4 套接字(二)

--LinuxCPP1504

-15.5 编程实践

--LinuxCPP1505

-第十五讲 网络编程--编程实践提交入口

课程文档

-课程PDF文件

LinuxCPP1104笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。