百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

C++异常处理

wptr33 2025-03-05 22:07 25 浏览

C++ 通过 throw 语句和 try...catch 语句实现对异常的处理。throw 语句的语法如下:

throw 表达式;

该语句拋出一个异常。异常是一个表达式,其值的类型可以是基本类型,也可以是类。

try...catch 语句的语法如下:

try {
语句组
}
catch(异常类型) {
异常处理代码
}
...
catch(异常类型) {
异常处理代码
}

catch 可以有多个,但至少要有一个。

不妨把 try 和其后
{}中的内容称作“try块”,把 catch 和其后{}中的内容称作“catch块”。

try...catch 语句的执行过程是:

  • 执行 try 块中的语句,如果执行的过程中没有异常拋出,那么执行完后就执行最后一个 catch 块后面的语句,所有 catch 块中的语句都不会被执行;
  • 如果 try 块执行的过程中拋出了异常,那么拋出异常后立即跳转到第一个“异常类型”和拋出的异常类型匹配的 catch 块中执行(称作异常被该 catch 块“捕获”),执行完后再跳转到最后一个 catch 块后面继续执行。

例如下面的程序:

#include using namespace std;int main(){    double m ,n;    cin >> m >> n;    try {        cout << "before dividing." << endl;        if( n == 0)            throw -1; //抛出int类型异常        else            cout << m / n << endl;        cout << "after dividing." << endl;    }    catch(double d) {        cout << "catch(double) " << d <<  endl;    }    catch(int e) {        cout << "catch(int) " << e << endl;    }    cout << "finished" << endl;    return 0;}

程序的运行结果如下:
9 6↙
before dividing.
1.5
after dividing.
finished

说明当 n 不为 0 时,try 块中不会拋出异常。因此程序在 try 块正常执行完后,越过所有的 catch 块继续执行,catch 块一个也不会执行。

程序的运行结果也可能如下:
9 0↙
before dividing.
catch\(int) -1
finished

当 n 为 0 时,try 块中会拋出一个整型异常。拋出异常后,try 块立即停止执行。该整型异常会被类型匹配的第一个 catch 块捕获,即进入
catch(int e)块执行,该 catch 块执行完毕后,程序继续往后执行,直到正常结束。

如果拋出的异常没有被 catch 块捕获,例如,将
catch(int e),改为catch(char e),当输入的 n 为 0 时,拋出的整型异常就没有 catch 块能捕获,这个异常也就得不到处理,那么程序就会立即中止,try...catch 后面的内容都不会被执行。

能够捕获任何异常的 catch 语句

如果希望不论拋出哪种类型的异常都能捕获,可以编写如下 catch 块:

catch(...) {
...
}

这样的 catch 块能够捕获任何还没有被捕获的异常。例如下面的程序:

#include using namespace std;int main(){    double m, n;    cin >> m >> n;    try {        cout << "before dividing." << endl;        if (n == 0)            throw - 1;  //抛出整型异常        else if (m == 0)            throw - 1.0;  //拋出 double 型异常        else            cout << m / n << endl;        cout << "after dividing." << endl;    }    catch (double d) {        cout << "catch (double)" << d << endl;    }    catch (...) {        cout << "catch (...)" << endl;    }    cout << "finished" << endl;    return 0;}

程序的运行结果如下:
9 0↙
before dividing.
catch (...)
finished

当 n 为 0 时,拋出的整型异常被
catchy(...)捕获。

程序的运行结果也可能如下:
0 6↙
before dividing.
catch (double) -1
finished

当 m 为 0 时,拋出一个 double 类型的异常。虽然
catch (double)catch(...)都能匹配该异常,但是catch(double)是第一个能匹配的 catch 块,因此会执行它,而不会执行catch(...)块。

由于
catch(...)能匹配任何类型的异常,它后面的 catch 块实际上就不起作用,因此不要将它写在其他 catch 块前面。

异常的再拋出

如果一个函数在执行过程中拋出的异常在本函数内就被 catch 块捕获并处理,那么该异常就不会拋给这个函数的调用者(也称为“上一层的函数”);如果异常在本函数中没有被处理,则它就会被拋给上一层的函数。例如下面的程序:

#include #include using namespace std;class CException{public:    string msg;    CException(string s) : msg(s) {}};double Devide(double x, double y){    if (y == 0)        throw CException("devided by zero");    cout << "in Devide" << endl;    return x / y;}int CountTax(int salary){    try {        if (salary < 0)            throw - 1;        cout << "counting tax" << endl;    }    catch (int) {        cout << "salary < 0" << endl;    }    cout << "tax counted" << endl;    return salary * 0.15;}int main(){    double f = 1.2;    try {        CountTax(-1);        f = Devide(3, 0);        cout << "end of try block" << endl;    }    catch (CException e) {        cout << e.msg << endl;    }    cout << "f = " << f << endl;    cout << "finished" << endl;    return 0;}

程序的输出结果如下:
salary < 0
tax counted
devided by zero
f=1.2
finished

CountTa 函数拋出异常后自行处理,这个异常就不会继续被拋给调用者,即 main 函数。因此在 main 函数的 try 块中,CountTax 之后的语句还能正常执行,即会执行
f = Devide(3, 0);

第 35 行,Devide 函数拋出了异常却不处理,该异常就会被拋给 Devide 函数的调用者,即 main 函数。拋出此异常后,Devide 函数立即结束,第 14 行不会被执行,函数也不会返回一个值,这从第 35 行 f 的值不会被修改可以看出。

Devide 函数中拋出的异常被 main 函数中类型匹配的 catch 块捕获。第 38 行中的 e 对象是用复制构造函数初始化的。

如果拋出的异常是派生类的对象,而 catch 块的异常类型是基类,那么这两者也能够匹配,因为派生类对象也是基类对象。

虽然函数也可以通过返回值或者传引用的参数通知调用者发生了异常,但采用这种方式的话,每次调用函数时都要判断是否发生了异常,这在函数被多处调用时比较麻烦。有了异常处理机制,可以将多处函数调用都写在一个 try 块中,任何一处调用发生异常都会被匹配的 catch 块捕获并处理,也就不需要每次调用后都判断是否发生了异常。

有时,虽然在函数中对异常进行了处理,但是还是希望能够通知调用者,以便让调用者知道发生了异常,从而可以作进一步的处理。在 catch 块中拋出异常可以满足这种需要。例如:

#include #include using namespace std;int CountTax(int salary){    try {        if( salary < 0 )            throw string("zero salary");        cout << "counting tax" << endl;    }    catch (string s ) {        cout << "CountTax error : " << s << endl;        throw; //继续抛出捕获的异常    }    cout << "tax counted" << endl;    return salary * 0.15;}int main(){    double f = 1.2;    try {        CountTax(-1);        cout << "end of try block" << endl;    }    catch(string s) {        cout << s << endl;    }    cout << "finished" << endl;    return 0;}

程序的输出结果如下:
CountTax error:zero salary
zero salary
finished

第 14 行的
throw;没有指明拋出什么样的异常,因此拋出的就是 catch 块捕获到的异常,即 string("zero salary")。这个异常会被 main 函数中的 catch 块捕获。

函数的异常声明列表

为了增强程序的可读性和可维护性,使程序员在使用一个函数时就能看出这个函数可能会拋出哪些异常,C++ 允许在函数声明和定义时,加上它所能拋出的异常的列表,具体写法如下:

void func() throw (int, double, A, B, C);

void func() throw (int, double, A, B, C){...}

上面的写法表明 func 可能拋出 int 型、double 型以及 A、B、C 三种类型的异常。异常声明列表可以在函数声明时写,也可以在函数定义时写。如果两处都写,则两处应一致。

如果异常声明列表如下编写:

void func() throw ();

则说明 func 函数不会拋出任何异常。

一个函数如果不交待能拋出哪些类型的异常,就可以拋出任何类型的异常。

函数如果拋出了其异常声明列表中没有的异常,在编译时不会引发错误,但在运行时, Dev C++ 编译出来的程序会出错;用 Visual Studio 2010 编译出来的程序则不会出错,异常声明列表不起实际作用。

C++标准异常类

C++ 标准库中有一些类代表异常,这些类都是从 exception 类派生而来的。常用的几个异常类如图 1 所示。



图1:常用的异常类


bad_typeid、bad_cast、bad_alloc、ios_base::failure、out_of_range 都是 exception 类的派生类。C++ 程序在碰到某些异常时,即使程序中没有写 throw 语句,也会自动拋出上述异常类的对象。这些异常类还都有名为 what 的成员函数,返回字符串形式的异常描述信息。使用这些异常类需要包含头文件 stdexcept。

下面分别介绍以上几个异常类。本节程序的输出以 Visual Studio 2010为准,Dev C++ 编译的程序输出有所不同。

1) bad_typeid

使用 typeid 运算符时,如果其操作数是一个多态类的指针,而该指针的值为 NULL,则会拋出此异常。

2) bad_cast

在用 dynamic_cast 进行从多态基类对象(或引用)到派生类的引用的强制类型转换时,如果转换是不安全的,则会拋出此异常。程序示例如下:

#include #include using namespace std;class Base{    virtual void func() {}};class Derived : public Base{public:    void Print() {}};void PrintObj(Base & b){    try {        Derived & rd = dynamic_cast (b);        //此转换若不安全,会拋出 bad_cast 异常        rd.Print();    }    catch (bad_cast & e) {        cerr << e.what() << endl;    }}int main(){    Base b;    PrintObj(b);    return 0;}

程序的输出结果如下:
Bad dynamic_cast!

在 PrintObj 函数中,通过 dynamic_cast 检测 b 是否引用的是一个 Derived 对象,如果是,就调用其 Print 成员函数;如果不是,就拋出异常,不会调用 Derived::Print。

3) bad_alloc

在用 new 运算符进行动态内存分配时,如果没有足够的内存,则会引发此异常。程序示例如下:

#include #include using namespace std;int main(){    try {        char * p = new char[0x7fffffff];  //无法分配这么多空间,会抛出异常    }    catch (bad_alloc & e)  {        cerr << e.what() << endl;    }    return 0;}

程序的输出结果如下:
bad allocation
ios_base::failure

在默认状态下,输入输出流对象不会拋出此异常。如果用流对象的 exceptions 成员函数设置了一些标志位,则在出现打开文件出错、读到输入流的文件尾等情况时会拋出此异常。此处不再赘述。

4) out_of_range

用 vector 或 string 的 at 成员函数根据下标访问元素时,如果下标越界,则会拋出此异常。例如:

#include #include #include #include using namespace std;int main(){    vector v(10);    try {        v.at(100) = 100;  //拋出 out_of_range 异常    }    catch (out_of_range & e) {        cerr << e.what() << endl;    }    string s = "hello";    try {        char c = s.at(100);  //拋出 out_of_range 异常    }    catch (out_of_range & e) {        cerr << e.what() << endl;    }    return 0;}

程序的输出结果如下:
invalid vector subscript
invalid string position

如果将
v.at(100)换成v[100],将s.at(100)换成s[100],程序就不会引发异常(但可能导致程序崩溃)。因为 at 成员函数会检测下标越界并拋出异常,而 operator[] 则不会。operator [] 相比 at 的好处就是不用判断下标是否越界,因此执行速度更快。

相关推荐

MySQL进阶五之自动读写分离mysql-proxy

自动读写分离目前,大量现网用户的业务场景中存在读多写少、业务负载无法预测等情况,在有大量读请求的应用场景下,单个实例可能无法承受读取压力,甚至会对业务产生影响。为了实现读取能力的弹性扩展,分担数据库压...

Postgres vs MySQL_vs2022连接mysql数据库

...

3分钟短文 | Laravel SQL筛选两个日期之间的记录,怎么写?

引言今天说一个细分的需求,在模型中,或者使用laravel提供的EloquentORM功能,构造查询语句时,返回位于两个指定的日期之间的条目。应该怎么写?本文通过几个例子,为大家梳理一下。学习时...

一文由浅入深带你完全掌握MySQL的锁机制原理与应用

本文将跟大家聊聊InnoDB的锁。本文比较长,包括一条SQL是如何加锁的,一些加锁规则、如何分析和解决死锁问题等内容,建议耐心读完,肯定对大家有帮助的。为什么需要加锁呢?...

验证Mysql中联合索引的最左匹配原则

后端面试中一定是必问mysql的,在以往的面试中好几个面试官都反馈我Mysql基础不行,今天来着重复习一下自己的弱点知识。在Mysql调优中索引优化又是非常重要的方法,不管公司的大小只要后端项目中用到...

MySQL索引解析(联合索引/最左前缀/覆盖索引/索引下推)

目录1.索引基础...

你会看 MySQL 的执行计划(EXPLAIN)吗?

SQL执行太慢怎么办?我们通常会使用EXPLAIN命令来查看SQL的执行计划,然后根据执行计划找出问题所在并进行优化。用法简介...

MySQL 从入门到精通(四)之索引结构

索引概述索引(index),是帮助MySQL高效获取数据的数据结构(有序),在数据之外,数据库系统还维护者满足特定查询算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构...

mysql总结——面试中最常问到的知识点

mysql作为开源数据库中的榜一大哥,一直是面试官们考察的重中之重。今天,我们来总结一下mysql的知识点,供大家复习参照,看完这些知识点,再加上一些边角细节,基本上能够应付大多mysql相关面试了(...

mysql总结——面试中最常问到的知识点(2)

首先我们回顾一下上篇内容,主要复习了索引,事务,锁,以及SQL优化的工具。本篇文章接着写后面的内容。性能优化索引优化,SQL中索引的相关优化主要有以下几个方面:最好是全匹配。如果是联合索引的话,遵循最...

MySQL基础全知全解!超详细无废话!轻松上手~

本期内容提醒:全篇2300+字,篇幅较长,可搭配饭菜一同“食”用,全篇无废话(除了这句),干货满满,可收藏供后期反复观看。注:MySQL中语法不区分大小写,本篇中...

深入剖析 MySQL 中的锁机制原理_mysql 锁详解

在互联网软件开发领域,MySQL作为一款广泛应用的关系型数据库管理系统,其锁机制在保障数据一致性和实现并发控制方面扮演着举足轻重的角色。对于互联网软件开发人员而言,深入理解MySQL的锁机制原理...

Java 与 MySQL 性能优化:MySQL分区表设计与性能优化全解析

引言在数据库管理领域,随着数据量的不断增长,如何高效地管理和操作数据成为了一个关键问题。MySQL分区表作为一种有效的数据管理技术,能够将大型表划分为多个更小、更易管理的分区,从而提升数据库的性能和可...

MySQL基础篇:DQL数据查询操作_mysql 查

一、基础查询DQL基础查询语法SELECT字段列表FROM表名列表WHERE条件列表GROUPBY分组字段列表HAVING分组后条件列表ORDERBY排序字段列表LIMIT...

MySql:索引的基本使用_mysql索引的使用和原理

一、索引基础概念1.什么是索引?索引是数据库表的特殊数据结构(通常是B+树),用于...