C++ 系列 -- 异常处理

程序错误

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

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

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

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

运行时错误如果放任不管,系统就会执行默认的操作,终止程序运行,也就是我们常说的程序崩溃

一个发生运行时错误的程序:

cpp 复制代码
#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 的值后程序崩溃

异常的处理流程

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

抛出异常

cpp 复制代码
char ch1 = str[100];  // [ ] 不会检查下标越界,不会抛出异常
char ch2 = str.at(100);  //  at() 函数执行过程中下标越界,抛出异常

也就是说 at() 函数会有抛出异常的动作,才会被捕获

在 C++ 中,我们使用 throw 关键字来显式地抛出异常,它的用法为:

cpp 复制代码
throw exceptionData;

exceptionData 是"异常数据"的意思,它可以包含任意的信息,完全有程序员决定。exceptionData 可以是 int、float、bool 等基本类型,也可以是指针、数组、字符串、结构体、类等聚合类型,请看下面的例子:

cpp 复制代码
char str[] = "http://c.biancheng.net";
char *pstr = str;

class Base{};
Base obj;

throw 100;  // int 类型
throw str;  // 数组类型
throw pstr;  // 指针类型
throw obj;  // 对象类型

捕获异常

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

cpp 复制代码
try{  
    // 可能抛出异常的语句  
}catch(exceptionType variable){  
    // 处理异常的语句  
}
  • trycatch都是 C++ 中的关键字,后跟语句块,不能省略{ }
  • try 检测 语句块有没有异常,一旦有异常抛出不会再执行 异常点后面的语句 ,并且会被后面的 catch 捕获
  • 如果 try 语句块没有检测到异常(没有异常抛出 ),那么就不会执行 catch 中的语句
  • catch 关键字后面的exceptionType variable指明了当前 catch 可以处理的异常类型 ,以及具体的出错信息

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

cpp 复制代码
#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;
}

运行结果:

cpp 复制代码
[2]out of bound!

检测到异常后程序的执行流会发生跳转,从异常点跳转到 catch 所在的位置,位于异常点之后的、并且在当前 try 块内的语句就都不会再执行了;即使 catch 语句成功地处理了错误,程序的执行流也不会再回退到异常点,所以这些语句永远都没有执行的机会了

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

异常类型和异常数据

cpp 复制代码
try{  
    // 可能抛出异常的语句  
}catch(exceptionType variable){  
    // 处理异常的语句  
}
  • exceptionType异常类型,它指明了当前的 catch 可以处理什么类型的异常;
  • variable 是一个变量,用来接收异常信息,当程序抛出异常时,会创建一份数据,这份数据包含了错误信息,程序员可以根据这些信息来判断到底出了什么问题
cpp 复制代码
#include <iostream>
#include <stdexcept>
using namespace std;

void func() {
    throw runtime_error("An error occurred");
}

int main()
{
    try {
        func();
    } catch (const runtime_error& e) {
        cout << "Caught an exception: " << e.what() << endl;
    }
}

异常类

C++ 语言本身或者标准库抛出的异常都是 exception 的子类,称为标准异常。你可以通过下面的语句来捕获所有的标准异常:

cpp 复制代码
try{
    //可能抛出异常的语句
}catch(exception &e){
    //处理异常的语句
}

之所以使用引用,是为了提高效率。如果不使用引用,就要经历一次对象拷贝(要调用拷贝构造函数)的过程。

exception 类位于 <exception> 头文件中,它被声明为:

cpp 复制代码
class exception{
public:
    exception () throw();  //构造函数
    exception (const exception&) throw();  //拷贝构造函数
    exception& operator= (const exception&) throw();  //运算符重载
    virtual ~exception() throw();  //虚析构函数
    virtual const char* what() const throw();  //虚函数
}

这里需要说明的是 what() 函数。what() 函数返回一个能识别异常的字符串,正如它的名字"what"一样,可以粗略地告诉你这是什么异常。不过C++标准并没有规定这个字符串的格式,各个编译器的实现也不同,所以 what() 的返回值仅供参考。

下图展示了 exception 类的继承层次:

exception 类的直接派生类:

异常名称 说 明
logic_error 逻辑错误
runtime_error 运行时错误
bad_alloc 使用 new 或 new[ ] 分配内存失败时抛出的异常
bad_typeid 使用 typeid 操作一个 NULL 指针,而且该指针是带有虚函数的类,这时抛出 bad_typeid 异常
bad_cast 使用 dynamic_cast 转换失败时抛出的异常
ios_base::failure io 过程中出现的异常
bad_exception 这是个特殊的异常,如果函数的异常列表里声明了 bad_exception 异常,当函数内部抛出了异常列表中没有的异常时,如果调用的 unexpected() 函数中抛出了异常,不论什么类型,都会被替换为 bad_exception 类型

logic_error 的派生类:

异常名称 说 明
length_error 试图生成一个超出该类型最大长度的对象时抛出该异常,例如 vector 的 resize 操作
domain_error 参数的值域错误,主要用在数学函数中,例如使用一个负值调用只能操作非负数的函数
out_of_range 超出有效范围
invalid_argument 参数不合适。在标准库中,当利用string对象构造 bitset 时,而 string 中的字符不是 0 或1 的时候,抛出该异常

runtime_error 的派生类:

异常名称 说 明
range_error 计算结果超出了有意义的值域范围
overflow_error 算术计算上溢
underflow_error 算术计算下溢
相关推荐
Charles Ray14 分钟前
C++学习笔记 —— 内存分配 new
c++·笔记·学习
重生之我在20年代敲代码14 分钟前
strncpy函数的使用和模拟实现
c语言·开发语言·c++·经验分享·笔记
迷迭所归处6 小时前
C++ —— 关于vector
开发语言·c++·算法
CV工程师小林6 小时前
【算法】BFS 系列之边权为 1 的最短路问题
数据结构·c++·算法·leetcode·宽度优先
white__ice7 小时前
2024.9.19
c++
天玑y7 小时前
算法设计与分析(背包问题
c++·经验分享·笔记·学习·算法·leetcode·蓝桥杯
姜太公钓鲸2337 小时前
c++ static(详解)
开发语言·c++
菜菜想进步7 小时前
内存管理(C++版)
c语言·开发语言·c++
Joker100858 小时前
C++初阶学习——探索STL奥秘——模拟实现list类
c++
科研小白_d.s8 小时前
vscode配置c/c++环境
c语言·c++·vscode