C++异常处理入门(try和catch)

目录
  • 捕获异常
  • 发生异常的位置

开发程序是一项“烧脑”的工作,程序员不但要经过长期的知识学习和思维训练,还要做到一丝不苟,注意每一个细节和边界。即使这样,也不能防止程序出错。

专家指出,长期作息不规律 + 用脑过度的危害很大,可能会诱发神经衰弱、失眠等疾病。我就是受害者之一,曾被失眠困扰了好几年,不但入睡困难,还容易早醒。程序员要注意劳逸结合,多去健身房,多跑步,多打球,多陪女朋友旅游等,千万不要熬夜,以为深夜写代码效率高,这样会透支年轻的身体。

程序的错误大致可以分为三种,分别是语法错误、逻辑错误和运行时错误:

1) 语法错误在编译和链接阶段就能发现,只有 100% 符合语法规则的代码才能生成可执行程序。语法错误是最容易发现、最容易定位、最容易排除的错误,程序员最不需要担心的就是这种错误。

2) 逻辑错误是说我们编写的代码思路有问题,不能够达到最终的目标,这种错误可以通过调试来解决。

3) 运行时错误是指程序在运行期间发生的错误,例如除数为 0、内存分配失败、数组越界、文件不存在等。C++ 异常(Exception)机制就是为解决运行时错误而引入的。

运行时错误如果放任不管,系统就会执行默认的操作,终止程序运行,也就是我们常说的程序崩溃(Crash)。C++ 提供了异常(Exception)机制,让我们能够捕获运行时错误,给程序一次“起死回生”的机会,或者至少告诉用户发生了什么再终止程序。

【例1】一个发生运行时错误的程序:

#include <iostream>
#include <string>
using namespace std;
int main(){
    string str = "http://c.biancheng.net";
    char ch1 = str[100];  //下标越界,ch1为垃圾值
    cout<<ch1<<endl;
    char ch2 = str.at(100);  //下标越界,抛出异常
    cout<<ch2<<endl;
    return 0;
}

运行代码,在控制台输出 ch1 的值后程序崩溃。下面我们来分析一下原因。

at() 是 string 类的一个成员函数,它会根据下标来返回字符串的一个字符。与[ ]不同,at() 会检查下标是否越界,如果越界就抛出一个异常;而[ ]不做检查,不管下标是多少都会照常访问。

所谓抛出异常,就是报告一个运行时错误,程序员可以根据错误信息来进一步处理。

上面的代码中,下标 100 显然超出了字符串 str 的长度。由于第 6 行代码不会检查下标越界,虽然有逻辑错误,但是程序能够正常运行。而第 8 行代码则不同,at() 函数检测到下标越界会抛出一个异常,这个异常可以由程序员处理,但是我们在代码中并没有处理,所以系统只能执行默认的操作,也即终止程序执行。

捕获异常

我们可以借助 C++ 异常机制来捕获上面的异常,避免程序崩溃。捕获异常的语法为:

try{
    // 可能抛出异常的语句
}catch(exceptionType variable){
    // 处理异常的语句
}

try和catch都是 C++ 中的关键字,后跟语句块,不能省略{ }。try 中包含可能会抛出异常的语句,一旦有异常抛出就会被后面的 catch 捕获。从 try 的意思可以看出,它只是“检测”语句块有没有异常,如果没有发生异常,它就“检测”不到。catch 是“抓住”的意思,用来捕获并处理 try 检测到的异常;如果 try 语句块没有检测到异常(没有异常抛出),那么就不会执行 catch 中的语句。

这就好比,catch 告诉 try:你去检测一下程序有没有错误,有错误的话就告诉我,我来处理,没有的话就不要理我!

catch 关键字后面的exceptionType variable指明了当前 catch 可以处理的异常类型,以及具体的出错信息。我们稍后再对异常类型展开讲解,当务之急是演示一下 try-catch 的用法,先让读者有一个整体上的认识。

【例2】修改上面的代码,加入捕获异常的语句:

#include <iostream>
#include <string>
#include <exception>
using namespace std;
int main(){
    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;
    }
    return 0;
}

运行结果:
(
[2]out of bound!

可以看出,第一个 try 没有捕获到异常,输出了一个没有意义的字符(垃圾值)。因为[ ]不会检查下标越界,不会抛出异常,所以即使有错误,try 也检测不到。换句话说,发生异常时必须将异常明确地抛出,try 才能检测到;如果不抛出来,即使有异常 try 也检测不到。所谓抛出异常,就是明确地告诉程序发生了什么错误。

第二个 try 检测到了异常,并交给 catch 处理,执行 catch 中的语句。需要说明的是,异常一旦抛出,会立刻被 try 检测到,并且不会再执行异常点(异常发生位置)后面的语句。本例中抛出异常的位置是第 17 行的 at() 函数,它后面的 cout 语句就不会再被执行,所以看不到它的输出。

说得直接一点,检测到异常后程序的执行流会发生跳转,从异常点跳转到 catch 所在的位置,位于异常点之后的、并且在当前 try 块内的语句就都不会再执行了;即使 catch 语句成功地处理了错误,程序的执行流也不会再回退到异常点,所以这些语句永远都没有执行的机会了。本例中,第 18 行代码就是被跳过的代码。

执行完 catch 块所包含的代码后,程序会继续执行 catch 块后面的代码,就恢复了正常的执行流。

为了演示「不明确地抛出异常就检测不到异常」,大家不妨将第 10 行代码改为char ch1 = str[100000000];,访问第 100 个字符可能不会发生异常,但是访问第 1 亿个字符肯定会发生异常了,这个异常就是内存访问错误。运行更改后的程序,会发现第 10 行代码产生了异常,导致程序崩溃了,这说明 try-catch 并没有捕获到这个异常。

关于「如何抛出异常」,我们将在下节讲解,这里重点是让大家明白异常的处理流程:

抛出(Throw)--> 检测(Try) --> 捕获(Catch)

发生异常的位置

异常可以发生在当前的 try 块中,也可以发生在 try 块所调用的某个函数中,或者是所调用的函数又调用了另外的一个函数,这个另外的函数中发生了异常。这些异常,都可以被 try 检测到。

1) 下面的例子演示了 try 块中直接发生的异常:

#include <iostream>
#include <string>
#include <exception>
using namespace std;
int main(){
    try{
        throw "Unknown Exception";  //抛出异常
        cout<<"This statement will not be executed."<<endl;
    }catch(const char* &e){
        cout<<e<<endl;
    }
    return 0;
}

运行结果:
Unknown Exception

throw关键字用来抛出一个异常,这个异常会被 try 检测到,进而被 catch 捕获。关于 throw 的用法,我们将在下节深入讲解,这里大家只需要知道,在 try 块中直接抛出的异常会被 try 检测到。

2) 下面的例子演示了 try 块中调用的某个函数中发生了异常:

#include <iostream>
#include <string>
#include <exception>
using namespace std;
void func(){
    throw "Unknown Exception";  //抛出异常
    cout<<"[1]This statement will not be executed."<<endl;
}
int main(){
    try{
        func();
        cout<<"[2]This statement will not be executed."<<endl;
    }catch(const char* &e){
        cout<<e<<endl;
    }
    return 0;
}

运行结果:
Unknown Exception

func() 在 try 块中被调用,它抛出的异常会被 try 检测到,进而被 catch 捕获。从运行结果可以看出,func() 中的 cout 和 try 中的 cout 都没有被执行。

3) try 块中调用了某个函数,该函数又调用了另外的一个函数,这个另外的函数抛出了异常:

#include <iostream>
#include <string>
#include <exception>
using namespace std;
void func_inner(){
    throw "Unknown Exception";  //抛出异常
    cout<<"[1]This statement will not be executed."<<endl;
}
void func_outer(){
    func_inner();
    cout<<"[2]This statement will not be executed."<<endl;
}
int main(){
    try{
        func_outer();
        cout<<"[3]This statement will not be executed."<<endl;
    }catch(const char* &e){
        cout<<e<<endl;
    }
    return 0;
}

运行结果:
Unknown Exception

发生异常后,程序的执行流会沿着函数的调用链往前回退,直到遇见 try 才停止。在这个回退过程中,调用链中剩下的代码(所有函数中未被执行的代码)都会被跳过,没有执行的机会了。

到此这篇关于C++异常处理入门(try和catch)的文章就介绍到这了,更多相关C++异常处理 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++编程异常处理中try和throw以及catch语句的用法

    若要在 C++ 中实现异常处理,你可以使用 try.throw 和 catch 表达式. 首先,使用 try 块将可能引发异常的一个或多个语句封闭起来. throw 表达式发出信号,异常条件(通常是错误)已在 try 块中发生.你可以使用任何类型的对象作为 throw 表达式的操作数.该对象一般用于传达有关错误的信息.大多数情况下,建议你使用 std::exception 类或标准库中定义的派生类之一.如果其中的类不合适,建议你从 std::exception 派生自己的异常类. 若要处理可能引

  • C++中try throw catch异常处理的用法示例

    前言 今天在开发过程中调用一个库函数结果库函数有throw操作,当前代码没有对throw进行捕获操作,导致进程在main 函数中捕获到异常导致进程crash.所以借此记录下c++关于try,throw,catch的用法. 程序运行时常会碰到一些异常情况,例如: 做除法的时候除数为 0: 用户输入年龄时输入了一个负数: 用 new 运算符动态分配空间时,空间不够导致无法分配: 访问数组元素时,下标越界:打开文件读取时,文件不存在. 这些异常情况,如果不能发现并加以处理,很可能会导致程序崩溃. 所谓

  • C++异常处理 try,catch,throw,finally的用法

    写在前面 所谓异常处理,即让一个程序运行时遇到自己无法处理的错误时抛出一个异常,希望调用者可以发现处理问题. 异常处理的基本思想是简化程序的错误代码,为程序键壮性提供一个标准检测机制. 也许我们已经使用过异常,但是你习惯使用异常了吗? 现在很多软件都是n*365*24小时运行,软件的健壮性至关重要. 内容导读 本文包括2个大的异常实现概念:C++的标准异常和SEH异常. C++标准异常: 也许你很高兴看到错误之后的Heap/Stack中对象被释放,可是如果没有呢? 又或者试想一下一个能解决的错误

  • C++之try catch 异常处理入门实例

    在程序设计过程中,我们总是希望自己设计的程序是天衣无缝的,但这几乎又是不可能的.即使程序编译通过,同时也实现了所需要的功能,也并不代表程序就已经完美无缺了,因为运行程序时还可能会遇到异常,例如当我们设计一个为用户计算除法的程序时,用户很有可能会将除数输入为零,又例如当我们需要打开一个文件的时候确发现该文件已经被删除了--类似的这种情况很有很多,针对这些特殊的情况,不加以防范是不行的. 我们通常希望自己编写的程序能够在异常的情况下也能作出相应的处理,而不至于程序莫名其妙地中断或者中止运行了.在设计

  • 详解C++异常处理(try catch throw)完全攻略

    程序运行时常会碰到一些异常情况,例如: 做除法的时候除数为 0: 用户输入年龄时输入了一个负数: 用 new 运算符动态分配空间时,空间不够导致无法分配: 访问数组元素时,下标越界:打开文件读取时,文件不存在. 这些异常情况,如果不能发现并加以处理,很可能会导致程序崩溃. 所谓"处理",可以是给出错误提示信息,然后让程序沿一条不会出错的路径继续执行:也可能是不得不结束程序,但在结束前做一些必要的工作,如将内存中的数据写入文件.关闭打开的文件.释放动态分配的内存空间等. 一发现异常情况就

  • C++异常处理入门(try和catch)

    目录 捕获异常 发生异常的位置 开发程序是一项"烧脑"的工作,程序员不但要经过长期的知识学习和思维训练,还要做到一丝不苟,注意每一个细节和边界.即使这样,也不能防止程序出错. 专家指出,长期作息不规律 + 用脑过度的危害很大,可能会诱发神经衰弱.失眠等疾病.我就是受害者之一,曾被失眠困扰了好几年,不但入睡困难,还容易早醒.程序员要注意劳逸结合,多去健身房,多跑步,多打球,多陪女朋友旅游等,千万不要熬夜,以为深夜写代码效率高,这样会透支年轻的身体. 程序的错误大致可以分为三种,分别是语法

  • Java中异常处理之try和catch代码块的使用

    Java try和catch的使用 尽管由Java运行时系统提供的默认异常处理程序对于调试是很有用的,但通常你希望自己处理异常.这样做有两个好处.第一,它允许你修正错误.第二,它防止程序自动终止.大多数用户对于在程序终止运行和在无论何时错误发生都会打印堆栈轨迹感到很烦恼(至少可以这么说).幸运的是,这很容易避免. 为防止和处理一个运行时错误,只需要把你所要监控的代码放进一个try块就可以了.紧跟着try块的,包括一个说明你希望捕获的错误类型的catch子句.完成这个任务很简单,下面的程序包含一个

  • Java异常处理try catch的基本使用

    目录 1. 异常 1.1 try…catch异常处理 1.2 多catch并行处理 1.3 throw和throws 关键字的使用 1.4 finally代码块 1.5 RuntimeException异常 1.6 自定义异常 总结 1. 异常 1.1 try…catch异常处理 try catch的异常处理的格式写法 : try{ 被检测的代码 可能发生异常的代码 }catch(异常类的类名 变量名){ 异常的处理方式 : 写什么都可以 定义变量,创建对象,调用方法,循环,判断... 只要写了

  • Objective-C 入门篇(推荐)

    目录 前言 基本数据类型 其他数据类型 id类型 BOOL类型 nil和Nil NSString(不可变字符串) NSMutableString(可变字符串) NSInteger.NSUInteger和NSNumber 集合 1. NSArray(不可变) 2. NSMutableArray(可变的) 字典 NSDictionary NSMutableDictionary NSSet && NSMutableSet 消息传递 类 访问修饰符 属性 方法 构造方法 创建类对象 self 继承

  • 轻松学习C#的异常处理

    异常是程序运行中发生的错误,异常处理是程序设计的一部分.错误的出现并不总是编写应用程序者的原因,有时候应用程序会因为终端用户的操作发生错误.无论如何,在编写程序前,都应该预测应用程序和代码中出现的错误.一般良好的编程规范也会避免一些不必要的程序错误的出现.         在项目的开发过程中,并不是所有的代码执行都和想象那样理想,总是避免不了异常的发生.这就需要编程语言的去处理这些异常,C#语言中有三种异常处理语句:         try...catch;//处理异常         try.

  • Java EE项目中的异常处理总结(一篇不得不看的文章)

    为什么要在J2EE项目中谈异常处理呢?可能许多java初学者都想说:"异常处理不就是try-.catch-finally吗?这谁都会啊!".笔者在初学java时也是这样认为的.如何在一个多层的j2ee项目中定义相应的异常类?在项目中的每一层如何进行异常处理?异常何时被抛出?异常何时被记录?异常该怎么记录?何时需要把checked Exception转化成unchecked Exception ,何时需要把unChecked Exception转化成checked Exception?异

  • Java异常处理学习心得

    程序中总是存在着各种问题,为了使在程序执行过程中能正常运行,使用Java提供的异常处理机制捕获可能发生的异常,对异常进行处理并使程序能正常运行.这就是Java的异常处理. 一.可捕获的异常 Java中可以捕获的异常分为可控式和运行时异常. 1. 可控式异常 在Java中把那些可以预知的错误,在程序编译时就能对程序中可能存在的错误进行处理,并给出具体的错误信息,这些错误称为可控式异常.常用的可控式异常如下: 异常说明 IOException 当发生某种I/O异常时,抛出此异常 SQLExcepti

  • Springboot全局异常捕获及try catch区别解析

    今天在码代码的时候突然想到这个问题,觉得有点困惑.在网上也翻阅不少帖子其中有一个帖子给了我一个思路,其实也是解释了基础概念. 概念一:try catch 语句在try里面捕获到异常之后会进入catch子句内,执行catch内的代码. 概念二:springboot中的全局异常处理在捕获到异常之后,异常代码之后的语句是不会再运行了的 那么从我一开始思路理解开始,这个时候我觉得这2个的功能用处都是在代码出现异常情况的时候对其进行处理,且向调用者(或者就是用户)返回一个友好的提示,那这2个有啥区别呢,重

  • 一篇文章带你入门Java运算符

    目录 算数运算符(Arithmetic operator) 关系运算符(Relational operator) 逻辑运算符(Logical operator) 赋值运算符(Assignment Operators) 三元运算符(Ternary operator) 运算符优先级 标识符的命名规则和规范 关键字定义和特点 保留字 总结 运算符时一种特殊的符号,用以表示数据的运算,赋值和比较等. 算数运算符 赋值运算符 关系运算符 逻辑运算符 位运算符 三元运算符 算数运算符(Arithmetic

随机推荐