C++ 异常处理(try catch)
在任何一门语言中都有异常的解释,这里就不做介绍了。
C++ 异常处理机制会涉及 try、catch、throw 三个关键字。
程序错误
程序的错误大致可以分为三种,分别是语法错误、逻辑错误和运行时错误:
1) 语法错误在编译和链接阶段就能发现,只有 100% 符合语法规则的代码才能生成可执行程序。语法错误是最容易发现、最容易定位、最容易排除的错误,程序员最不需要担心的就是这种错误。
2) 逻辑错误是说我们编写的代码思路有问题,不能够达到最终的目标,这种错误可以通过调试来解决。
3) 运行时错误是指程序在运行期间发生的错误,例如除数为 0、内存分配失败、数组越界、文件不存在等。C++ 异常(Exception)机制就是为解决运行时错误而引入的。
运行时错误如果放任不管,系统就会执行默认的操作,终止程序运行,也就是我们常说的程序崩溃(Crash)。C++ 提供了异常(Exception)机制,让我们能够捕获运行时错误,给程序一次“起死回生”的机会,或者至少告诉用户发生了什么再终止程序。
异常处理基本思想
C++之父Bjarne Stroustrup在《The C++ Programming Language》中讲到:一个库的作者可以检测出发生了运行时错误,但一般不知道怎样去处理它们(因为和用户具体的应用有关);另一方面,库的用户知道怎样处理这些错误,但却无法检查它们何时发生(如果能检测,就可以再用户的代码里处理了,不用留给库去发现)。
Bjarne Stroustrup说:提供异常的基本目的就是为了处理上面的问题。基本思想是:让一个函数在发现了自己无法处理的错误时抛出(throw)一个异常,然后它的(直接或者间接)调用者能够处理这个问题。
The fundamental idea is that a function that finds a problem it cannot cope with throws an exception, hoping that its (direct or indirect) caller can handle the problem.
也就是《C++ primer》中说的:将问题检测和问题处理相分离。
Exceptions let us separate problem detection from problem resolution
C语言中的处理错误方式
在C语言的世界中,对错误的处理总是围绕着两种方法:一是使用整型的返回值标识错误;二是使用errno宏(可以简单的理解为一个全局整型变量)去记录错误。当然C++中仍然是可以用这两种方法的。
这两种方法最大的缺陷就是会出现不一致问题。例如有些函数返回1表示成功,返回0表示出错;而有些函数返回0表示成功,返回非0表示出错。
还有一个缺点就是函数的返回值只有一个,你通过函数的返回值表示错误代码,那么函数就不能返回其他的值。当然,你也可以通过指针或者C++的引用来返回另外的值,但是这样可能会令你的程序略微晦涩难懂。
异常为什么好
在如果使用异常处理的优点有以下几点:
1. 函数的返回值可以忽略,但异常不可忽略。如果程序出现异常,但是没有被捕获,程序就会终止,这多少会促使程序员开发出来的程序更健壮一点。而如果使用C语言的error宏或者函数返回值,调用者都有可能忘记检查,从而没有对错误进行处理,结果造成程序莫名其面的终止或出现错误的结果。
2. 整型返回值没有任何语义信息。而异常却包含语义信息,有时你从类名就能够体现出来。
3. 整型返回值缺乏相关的上下文信息。异常作为一个类,可以拥有自己的成员,这些成员就可以传递足够的信息。
4. 异常处理可以在调用跳级。这是一个代码编写时的问题:假设在有多个函数的调用栈中出现了某个错误,使用整型返回码要求你在每一级函数中都要进行处理。而使用异常处理的栈展开机制,只需要在一处进行处理就可以了,不需要每级函数都处理。
C++中使用异常时应注意的问题
任何事情都是两面性的,异常有好处就有坏处。如果你是C++程序员,并且希望在你的代码中使用异常,那么下面的问题是你要注意的。
1. 性能问题。这个一般不会成为瓶颈,但是如果你编写的是高性能或者实时性要求比较强的软件,就需要考虑了。
2. 指针和动态分配导致的内存回收问题:在C++中,不会自动回收动态分配的内存,如果遇到异常就需要考虑是否正确的回收了内存。在java中,就基本不需要考虑这个,有垃圾回收机制。
3. 函数的异常抛出列表:java中是如果一个函数没有在异常抛出列表中显式指定要抛出的异常,就不允许抛出;可是在C++中是如果你没有在函数的异常抛出列表指定要抛出的异常,意味着你可以抛出任何异常。
4. C++中编译时不会检查函数的异常抛出列表。这意味着你在编写C++程序时,如果在函数中抛出了没有在异常抛出列表中声明的异常,编译时是不会报错的。而在java中会检查。
5. 在java中,抛出的异常都要是一个异常类;但是在C++中,你可以抛出任何类型,你甚至可以抛出一个整型。(当然,在C++中如果你catch中接收时使用的是对象,而不是引用的话,那么你抛出的对象必须要是能够复制的。这是语言的要求,不是异常处理的要求)。
6. 在C++中是没有finally关键字的。而java和python中都是有finally关键字的。
捕获异常
抛出异常用throw,捕获用try……catch。
我们可以借助 C++ 异常机制来捕获上面的异常,避免程序崩溃。捕获异常的语法为:
try{// 可能抛出异常的语句
}catch(exceptionType variable){// 处理异常的语句
}
string str = "http://c.biancheng.net";try{
char ch1 = str[100];//数组下标越界访问cout<<ch1<<endl;
}catch(exception e){cout<<"[1]out of bound!"<<endl;
}try{char ch2 = str.at(100);cout<<ch2<<endl;
}catch(exception &e){ //exception类位于<exception>头文件中cout<<"[2]out of bound!"<<endl;
}
第一个 try 没有捕获到异常,输出了一个没有意义的字符(垃圾值)。因为[ ]
不会检查下标越界,不会抛出异常,所以即使有错误,try 也检测不到。换句话说,发生异常时必须将异常明确地抛出,try 才能检测到;如果不抛出来,即使有异常 try 也检测不到。所谓抛出异常,就是明确地告诉程序发生了什么错误。
第二个 try 检测到了异常,并交给 catch 处理,执行 catch 中的语句。需要说明的是,异常一旦抛出,会立刻被 try 检测到,并且不会再执行异常点(异常发生位置)后面的语句。本例中抛出异常的位置是第 17 行的 at() 函数,它后面的 cout 语句就不会再被执行,所以看不到它的输出。
C++ 标准的异常
C++ 提供了一系列标准的异常,定义在 <exception> 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,如下所示:
下表是对上面层次结构中出现的每个异常的说明:
异常 | 描述 |
---|---|
std::exception | 该异常是所有标准 C++ 异常的父类。 |
std::bad_alloc | 该异常可以通过 new 抛出。 |
std::bad_cast | 该异常可以通过 dynamic_cast 抛出。 |
std::bad_exception | 这在处理 C++ 程序中无法预期的异常时非常有用。 |
std::bad_typeid | 该异常可以通过 typeid 抛出。 |
std::logic_error | 理论上可以通过读取代码来检测到的异常。 |
std::domain_error | 当使用了一个无效的数学域时,会抛出该异常。 |
std::invalid_argument | 当使用了无效的参数时,会抛出该异常。 |
std::length_error | 当创建了太长的 std::string 时,会抛出该异常。 |
std::out_of_range | 该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator[]()。 |
std::runtime_error | 理论上不可以通过读取代码来检测到的异常。 |
std::overflow_error | 当发生数学上溢时,会抛出该异常。 |
std::range_error | 当尝试存储超出范围的值时,会抛出该异常。 |
std::underflow_error | 当发生数学下溢时,会抛出该异常。 |
案例:
1. 除0
#include <iostream>
using namespace std;
double division(int a, int b){if( b == 0 ){throw "Division by zero condition!";//const char*}return (a/b);
}
int main (){int x = 50;int y = 0;double z = 0;try {z = division(x, y);cout << z << endl;}catch (const char* msg) {cerr << msg << endl;}return 0;
}
由于我们抛出了一个类型为 const char* 的异常,因此,当捕获该异常时,我们必须在 catch 块中使用 const char*。
2. 定义新的异常
您可以通过继承和重载 exception 类来定义新的异常。下面的实例演示了如何使用 std::exception 类来实现自己的异常:
例2:依照C++标准实现自定义异常类myException并将throw语句封装到函数check()中
涉及到的更改正如标题所述,(1)重写基类的what()函数,返回错误信息;(2)将throw myException()封装到check()函数中;(3)允许check()函数抛出myException类型的异常。代码如下:C++函数声明后面加throw()的作用!
#include<exception>
#include<iostream>
using namespace std; class myException:public exception{ public: const char* what()const throw(){ //throw () 表示不允许任何异常产生 return "ERROR! Don't divide a number by integer zero.\n"; }
};
void check(int y) throw(myException){ //throw (myException)表示只允许myException的异常发生 if(y==0) throw myException();
} int main()
{ int x=100,y=0; try{ check(y); cout<<x/y; }catch(myException& me){ cout<<me.what(); } system("pause"); return 0;
}
下面的编译也能通过:
void check(int y) throw()
{ if(y==0) throw myException(); // // 程序会在这里崩溃.(编者注:如果该异常被处理,不会崩溃)
}
成员函数声明后面跟上throw(),表示告诉类的使用者:我的这个方法不会抛出异常,所以,在使用该方法的时候,不必把它至于 try/catch 异常处理块中。
声明一个不抛出异常的函数后,你有责任保证在你的函数的实现里面不会抛出异常。
函数后面声明 throw() 只是接口的提供者和接口的使用者间的默契或称协议。
作者:无涯明月
上篇: C++函数声明后面加throw()的作用!
C++ 异常处理(try catch)相关推荐
- Java 异常处理 try catch finally throws throw 的使用和解读(一)
最近的一个内部表决系统开发过程中, 发现对异常处理还存在一些模棱两可的地方, 所以想着整理一下 主要涉及到: 1.try catch finally throws throw 的使用和解读 2.自定义 ...
- js的异常处理 try catch
js的异常处理 try catch 参考文章: (1)js的异常处理 try catch (2)https://www.cnblogs.com/luxd/p/6148545.html 备忘一下.
- 一个存储过程帮你了解 事务(TRAN)、异常处理(TRY/CATCH)、@@ERROR
/* 事务:begin transaction开始事务 . commit transaction 提交事务. rollback transaction 回滚事务. SAVE TRAN 保存事务从本 ...
- C#异常处理try catch
原文地址:点击打开链接 本文翻译自CodeProject上的一篇文章,原文地址. 目录 介绍 做最坏的打算提前检查不要信任外部数据可信任的设备:摄像头.鼠标以及键盘"写操作"同样可 ...
- 异常处理(try/catch)
#include<stdio.h> int main() {try{printf("打印块1代码执行\n");throw 10;//产生一个异常 }catch(int& ...
- js中的异常处理try...catch使用介绍
在JavaScript可以使用try...catch来进行异常处理.例如: 复制代码 代码如下: try { foo.bar();} catch (e) { alert(e.name + " ...
- 【转】 ABAP中的异常处理 - TRY CATCH的使用实例
在平时的ABAP开发中,需要捕获的异常通常为两种,一种是执行SQL,比如主键重复,INSERT语句字段类型不匹配等.还有就是RFC的通信错误,比如不能进行远程连接等.通常可以这么处理: 1.数据库异常 ...
- 异常处理try...catch...throw
C++ 引入了异常处理机制.其基本思想是:函数 A 在执行过程中发现异常时可以不加处理,而只是"拋出一个异常"给 A 的调用者,假定为函数 B. 拋出异常而不加处理会导致函数 A ...
- 小白的JAVA学习笔记(九)---异常处理(try/catch/finally,ducking)
在编写程序的过程中不可能是一帆风顺的,我们可能会调用一些有风险的方法,也就是这些方法可能会发生异常.当我们知道调用某个方法有一定的可能性发生异常时,我们可以提前做好准备来处理问题程序.那我们怎么知道哪 ...
- C# 异常处理(Catch Throw)IL分析
1.catch throw的几种形式及性能影响: private void Form1_Click(object sender, EventArgs e){try{}catch{throw;}}pri ...
最新文章
- NC:中国药科郝海平和郑啸发现饮食-微生物互作缓解肠损伤
- 牛津-阿斯利康疫苗与出血性疾病风险轻微升高有关 |《自然-医学》论文
- ZooKeeper基础学习
- Docker - Tips
- python pillow库_python pillow模块用法
- javaScript入门之常用事件
- 自嗨锅要持续嗨,还得碾碎这四个“绊脚石”
- poj 1006 java_POJ 1006 Biorhythms 数论-(孙子定理)
- 四中方式实现单例模式
- mysql 外键(foreign key)的详解和实例_MySQL数据库外键
- Chrome谷歌插件开发-01
- 012-ViewState状态保持
- Linux 使用root用户登录系统,并查看当前的路径。 查看当前目录下面的所有文件(包括隐藏文件)。 在当前目录下,查看根目录中的目录结构。
- 聚宽macd底背离_很多散户可能永远都不会知道:MACD月线金叉,每一次MACD月金叉都会带来一波牛市...
- 数学建模论文写作方法
- 基于STM32的ESP8266天气时钟(2)--------MCU获取天气数据
- 吉尔伯特定律(转载)
- SPSS聚类分析(含k-均值聚类,系统聚类和二阶聚类)
- Linux 系统挂载大硬盘(>2TB)及默认登录目录修改
- EEG信号处理与分析常用工具包介绍