C++学习第三十课:C++异常处理机制应用

news2024/9/28 21:56:42

在这里插入图片描述

一、引言

在编程过程中,我们经常会遇到一些无法预见或难以预料的特殊情况,这些情况统称为“异常”。异常可能是由用户输入错误、资源不足、硬件故障或程序逻辑错误等原因引起的。当这些异常情况发生时,如果程序没有适当的处理机制,可能会导致程序崩溃、数据丢失或其他不可预测的后果。因此,异常处理是编程中非常重要的一部分。

1. 异常处理的概念

异常处理是一种编程技术,用于在程序运行时检测和响应异常情况。在C++中,异常处理是通过异常类(Exception Classes)和异常处理机制(Exception Handling Mechanism)来实现的。当异常情况发生时,程序会抛出一个异常对象,这个异常对象包含了关于异常的信息(如异常的类型和描述)。然后,程序会寻找一个能够处理这个异常的代码块(catch块),并执行其中的代码来响应这个异常。

2. 为什么需要异常处理

在程序中引入异常处理有以下几个好处:

提高程序的健壮性:通过捕获并处理异常情况,可以防止程序因未处理的异常而崩溃,从而提高程序的健壮性。
简化错误处理:使用异常处理可以将错误处理代码与正常的业务逻辑代码分离,使代码更加清晰、易读和易维护。
支持多层调用:在多层调用的函数中,如果底层函数发生了异常,可以使用异常处理机制将异常逐层向上传递,直到找到合适的处理代码。

3. C++中异常处理的历史与演变

C++的异常处理机制是在C++98标准中引入的,并成为了C++语言的一个重要组成部分。在C++98之前,C++程序员通常使用错误码或返回值来表示和处理错误。但是,这种方法存在一些问题,如错误处理代码与业务逻辑代码混杂在一起、难以处理多层调用中的错误等。因此,C++98标准引入了异常处理机制,为C++程序员提供了一种更加优雅和强大的错误处理方式。

二、异常处理的基本概念

在C++中,异常处理是一种处理程序运行时错误或异常情况的机制。它允许程序在检测到错误时,采取适当的措施,而不是简单地崩溃或产生不可预测的结果。本章节将介绍异常处理的基本概念,包括异常与错误、异常处理的组成部分等。

1. 异常与错误

在编程中,错误(Error)和异常(Exception)是两个经常被提及的概念,但它们之间有一些区别。

  • 错误(Error):通常指的是程序在运行时遇到的无法继续执行的情况,如文件不存在、内存不足等。错误通常是由外部因素引起的,并且是不可预见的。
  • 异常(Exception):是程序在运行时遇到的一种特殊情况,它表示程序遇到了一个预期之外的问题,但这个问题是可以通过编程来处理的。异常通常是由程序内部因素引起的,如除数为零、数组越界等。

在C++中,我们使用异常处理机制来处理这些异常情况。

2. 异常处理的组成部分

C++的异常处理机制主要由以下几个部分组成:

  • try块:包含可能会抛出异常的代码。当try块中的代码执行时,如果发生了异常情况,就会抛出一个异常对象。
  • catch块:用于捕获并处理异常。当try块中的代码抛出异常时,程序会查找与当前异常类型匹配的catch块。如果找到了匹配的catch块,程序就会跳转到该catch块并执行其中的代码。
  • throw语句:用于抛出异常。当检测到异常情况时,程序会执行throw语句,并抛出一个异常对象。这个异常对象包含了关于异常的信息,如异常的类型和描述。

3. 示例代码

下面是一个简单的示例代码,演示了如何在C++中使用异常处理机制:

#include <iostream>
#include <stdexcept> // 包含std::runtime_error类

// 一个可能抛出异常的函数
int divide(int a, int b) {
    if (b == 0) {
        throw std::runtime_error("Division by zero!"); // 抛出异常
    }
    return a / b;
}

int main() {
    try {
        int result = divide(10, 0); // 尝试执行可能抛出异常的代码
        std::cout << "Result: " << result << std::endl;
    } catch (const std::runtime_error& e) { // 捕获并处理异常
        std::cerr << "Error: " << e.what() << std::endl; // 输出异常信息
    }
    return 0;
}

在这个示例中,我们定义了一个名为divide的函数,该函数在除数为零时会抛出一个std::runtime_error异常。在main函数中,我们使用try-catch块来捕获并处理这个异常。当除数为零时,程序会执行throw语句抛出一个异常对象,然后跳转到与这个异常类型匹配的catch块中执行代码。在catch块中,我们输出了异常信息。这样,我们就可以通过异常处理机制来避免程序因未处理的异常而崩溃。

三、try-catch机制

在C++中,try-catch机制是实现异常处理的核心。它允许我们定义一段可能抛出异常的代码(try块),并指定一个或多个处理程序(catch块)来捕获和处理这些异常。本章节将详细介绍try-catch机制的使用方法和原理。

1. try块的作用与编写规范

try块是包含可能抛出异常的代码的部分。当try块中的代码执行时,如果发生异常情况,会抛出一个异常对象。这个异常对象会被传递给随后的catch块进行处理。

try块的编写规范包括:

  • 将可能抛出异常的代码放在try块中。
  • 确保try块中的代码逻辑清晰,避免在try块中执行过多的操作,以便更容易地确定异常发生的位置。

2. catch块的语法与多catch处理

catch块用于捕获并处理try块中抛出的异常。catch块的语法如下:

catch (type exceptionName) {
    // 处理异常的代码
}

其中,type是异常的类型,exceptionName是异常对象的名称(可以省略)。当try块中的代码抛出异常时,程序会查找与异常类型匹配的catch块。如果找到了匹配的catch块,程序就会跳转到该catch块并执行其中的代码。

如果可能抛出多种类型的异常,可以使用多个catch块来分别处理它们。多个catch块可以按照任意顺序排列,但通常建议将更具体的异常类型放在前面,以便更准确地捕获和处理异常。

3. 捕获特定类型的异常

在catch块中,我们可以指定要捕获的异常类型。只有与指定类型匹配的异常才会被该catch块捕获。例如,以下代码只捕获std::runtime_error类型的异常:

try {
    // 可能抛出异常的代码
} catch (const std::runtime_error& e) {
    // 处理std::runtime_error类型的异常
}

4. 捕获基类与派生类的异常

在C++中,如果一个catch块指定了一个基类类型的异常,那么它也可以捕获该基类的派生类类型的异常。这是因为派生类对象可以隐式地转换为基类对象。以下是一个示例:

class MyException : public std::exception { /* ... */ };

try {
    // 可能抛出MyException或std::exception类型的异常
} catch (const std::exception& e) {
    // 可以捕获MyException或std::exception类型的异常
}

在这个示例中,如果try块中抛出了MyException类型的异常,那么它也会被catch (const std::exception& e)块捕获,因为MyExceptionstd::exception的派生类。

5. 异常匹配规则

当try块中的代码抛出异常时,程序会按照catch块的顺序查找与异常类型匹配的catch块。一旦找到了匹配的catch块,程序就会跳转到该catch块并执行其中的代码。如果所有catch块都与异常类型不匹配,那么程序会调用标准库中的std::terminate函数来终止程序。

6. 示例代码:演示try-catch的基本使用

以下是一个示例代码,演示了如何使用try-catch机制来处理不同类型的异常:

#include <iostream>
#include <stdexcept>

class MyCustomException : public std::exception {
public:
    const char* what() const noexcept override {
        return "MyCustomException occurred!";
    }
};

int main() {
    try {
        // 尝试执行可能抛出异常的代码
        throw MyCustomException(); // 抛出自定义异常
        // throw std::runtime_error("Runtime error occurred!"); // 抛出运行时异常
    } catch (const MyCustomException& e) {
        // 处理自定义异常
        std::cerr << "Caught a MyCustomException: " << e.what() << std::endl;
    } catch (const std::runtime_error& e) {
        // 处理运行时异常
        std::cerr << "Caught a runtime error: " << e.what() << std::endl;
    } catch (...) {
        // 捕获所有其他类型的异常
        std::cerr << "Caught an unknown exception" << std::endl;
    }
    return 0;
}

四、异常的传播与终止

在C++中,当异常被抛出后,它会沿着函数调用栈(Call Stack)向上传播,直到找到一个能够处理它的catch块。如果没有找到匹配的catch块,程序会立即终止执行。这一章节将详细解释异常的传播机制和终止行为,并通过代码实例进行说明。

1. 异常的传播机制

当在函数内部发生异常时,程序会立即跳出当前的函数,并将异常对象沿着函数调用栈向上传递。这个传播过程会一直持续,直到找到一个能够处理该异常的catch块。如果在所有的函数调用栈帧中都没有找到匹配的catch块,程序会调用一个名为std::terminate的函数,该函数通常会调用std::abort来终止程序。

2. 异常的终止行为

如果没有合适的catch块来处理异常,程序会终止执行。这通常会导致一些资源没有得到正确的清理,如动态分配的内存、打开的文件等。为了避免这种情况,C++提供了栈展开(Stack Unwinding)的机制。当异常被抛出时,C++会尝试自动调用当前函数以及所有已经调用但还未返回的函数中的局部对象的析构函数,以释放它们所占用的资源。这个过程称为栈展开。

注意:栈展开是C++异常处理机制的一个重要部分,但它并不是必须的。在某些情况下,编译器可能会选择不进行栈展开,而是直接调用std::terminate来终止程序。

3. 示例代码

下面是一个示例代码,演示了异常的传播和终止行为:

#include <iostream>
#include <stdexcept>

void functionC() {
    throw std::runtime_error("Exception in functionC");
}

void functionB() {
    functionC(); // 调用functionC,可能会抛出异常
    std::cout << "This line will not be executed if an exception is thrown in functionC" << std::endl;
}

void functionA() {
    try {
        functionB(); // 调用functionB,如果functionB中抛出异常,会传播到这里
    } catch (const std::runtime_error& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }
    std::cout << "This line will always be executed, regardless of whether an exception is thrown" << std::endl;
}

int main() {
    functionA(); // 调用functionA
    return 0;
}

五、自定义异常类

在C++中,除了使用标准库提供的异常类(如std::runtime_errorstd::exception等)外,我们还可以定义自己的异常类来更好地处理特定的异常情况。自定义异常类通常继承自std::exception或其子类,并可以提供额外的数据成员和成员函数来存储和访问与异常相关的特定信息。

1. 自定义异常类的基本结构

自定义异常类通常包含以下内容:

  • 继承自std::exception或其子类。
  • 重写what()成员函数,用于返回描述异常的字符串。
  • 可以包含额外的数据成员来存储与异常相关的特定信息。
  • 可以提供额外的成员函数来访问这些额外的信息。

2. 示例代码

下面是一个自定义异常类的示例代码,用于处理文件读写过程中的特定错误:

#include <iostream>
#include <stdexcept>
#include <string>

// 自定义文件异常类
class FileException : public std::runtime_error {
private:
    std::string fileName; // 额外的数据成员,存储文件名

public:
    // 构造函数
    FileException(const std::string& fileName, const std::string& what)
        : std::runtime_error(what), fileName(fileName) {}

    // 访问文件名
    const std::string& getFileName() const {
        return fileName;
    }
};

// 模拟文件读写函数,可能会抛出FileException异常
void readFile(const std::string& fileName) {
    // 这里只是模拟,实际情况下会根据文件读写操作的结果来决定是否抛出异常
    if (fileName == "nonexistent.txt") {
        throw FileException(fileName, "File not found");
    }
    // ... 其他文件读取逻辑
}

int main() {
    try {
        readFile("nonexistent.txt"); // 尝试读取一个不存在的文件
    } catch (const FileException& e) {
        std::cerr << "Caught FileException: " << e.what() << " for file " << e.getFileName() << std::endl;
    } catch (const std::exception& e) {
        // 捕获其他类型的异常(如果需要的话)
        std::cerr << "Caught an exception: " << e.what() << std::endl;
    }

    return 0;
}

解释

  1. 我们定义了一个名为FileException的自定义异常类,它继承自std::runtime_error。这个类有一个额外的数据成员fileName,用于存储与异常相关的文件名。
  2. FileException类中,我们重写了what()成员函数,以返回描述异常的字符串。同时,我们还提供了一个getFileName()成员函数,用于访问额外的数据成员fileName
  3. readFile函数中,我们模拟了文件读取操作。如果文件名是"nonexistent.txt",则抛出一个FileException异常,并传递文件名和异常描述作为参数。
  4. main函数中,我们使用try-catch块来捕获并处理可能抛出的异常。我们首先尝试读取一个不存在的文件,如果抛出FileException异常,我们捕获它并打印出异常描述和文件名。如果没有捕获到FileException异常,但捕获到其他类型的异常(如果需要的话),我们也打印出异常描述。

六、异常安全

在C++编程中,异常安全是一个重要的概念,它涉及到函数或代码块在发生异常时如何保持其不变性(Invariance)。异常安全通常分为几个级别,包括基本保证(Basic Guarantee)、强保证(Strong Guarantee)和无泄漏保证(No-Leak Guarantee)。本章节将介绍这些概念,并通过代码实例进行解释。

1. 异常安全的级别

  • 基本保证(Basic Guarantee):在发生异常时,不引入新的错误,不破坏已有对象的不变式(Invariant),但可能不保持程序状态不变(即可能回滚到部分完成的状态)。
  • 强保证(Strong Guarantee):在发生异常时,程序状态与函数调用前完全相同(即要么成功完成,要么保持原样)。这通常通过“复制-构造-交换”或“先构造后交换”等技术实现。
  • 无泄漏保证(No-Leak Guarantee):无论是否发生异常,资源(如内存、文件句柄等)都不会泄漏。这是所有异常安全级别都应当满足的基本要求。

2. 示例代码 - 强保证

下面是一个使用“复制-构造-交换”技术实现强保证的示例代码:

#include <iostream>
#include <string>
#include <utility> // for std::swap

class MyResource {
public:
    MyResource(const std::string& name) : resourceName(name), isAllocated(true) {}
    MyResource(const MyResource& other) : resourceName(other.resourceName), isAllocated(true) {
        // 假设这里进行了深拷贝或其他资源分配操作
        std::cout << "Copying resource: " << resourceName << std::endl;
    }
    MyResource& operator=(MyResource other) { // 注意这里是按值传递
        std::swap(resourceName, other.resourceName);
        std::swap(isAllocated, other.isAllocated);
        // other对象现在包含了之前的资源,但即将被销毁,从而自动释放资源
        return *this;
    }
    ~MyResource() {
        if (isAllocated) {
            // 释放资源
            std::cout << "Deleting resource: " << resourceName << std::endl;
            isAllocated = false;
        }
    }

    std::string resourceName;
    bool isAllocated;
};

class MyResourceHolder {
public:
    MyResourceHolder(const std::string& name) : resource(new MyResource(name)) {}
    // 使用复制-构造-交换实现强保证的异常安全赋值运算符
    MyResourceHolder& operator=(MyResourceHolder other) {
        std::swap(resource, other.resource);
        return *this;
    }
    // ... 其他成员函数 ...

private:
    std::unique_ptr<MyResource> resource;
};

int main() {
    MyResourceHolder holder1("Resource1");
    MyResourceHolder holder2("Resource2");
    
    try {
        // 假设这里可能抛出异常
        holder1 = holder2; // 使用强保证的赋值运算符
    } catch (...) {
        // 如果发生异常,holder1和holder2的状态保持不变
    }

    return 0;
}

解释

在上面的示例中,MyResource类模拟了一个需要管理的资源。其构造函数、析构函数和复制构造函数负责资源的分配和释放。MyResourceHolder类则持有MyResource的指针,并通过智能指针std::unique_ptr来管理其生命周期。

重要的是MyResourceHolder的赋值运算符实现。它接受一个按值传递的MyResourceHolder对象(即other)。由于other是通过值传递的,因此它的构造函数会被调用,创建一个新的MyResource对象(如果需要的话)。然后,通过std::swap交换两个MyResourceHolder对象的内部指针。这样,如果后续代码(包括析构函数)抛出异常,other对象(现在包含旧的资源)将在赋值运算符返回后被销毁,从而安全地释放资源。原始对象*this则获得了新的资源,无需担心资源泄漏。

这种技术确保了即使在赋值运算符执行过程中发生异常,程序状态也能保持一致(即强保证)。同时,由于使用了智能指针,无论是否发生异常,资源都不会泄漏(即无泄漏保证)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1662865.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL数据库表的创建DDL语句(21-25)

21.用户反馈表 CREATE TABLE 7_feedback (feedbackId int(11) NOT NULL AUTO_INCREMENT COMMENT ID,feedbackType int(4) NOT NULL DEFAULT 0 COMMENT 反馈类型&#xff0c;内容来自源系统基础数据表,userId int(11) DEFAULT NULL COMMENT 反馈者ID,creatTime datetime NOT NU…

商场综合体能源监管平台,实现能源高效管理

商场作为大型综合体建筑&#xff0c;其能源消耗一直是备受关注的问题。为了有效管理商场能耗&#xff0c;提高商场能源效率&#xff0c;商场综合体能源监管平台应运而生。 商场综合体能源监管平台可通过软硬件一起进行节能监管&#xff0c;硬件设备包括各种传感器、监测仪表和…

ArcGIS10.2系列许可到期解决方案

本文手机码字&#xff0c;不排版了。 昨晚&#xff08;2021\12\17&#xff09;12点后&#xff0c;收到很多学员反馈 ArcGIS10.2系列软件突然崩溃。更有的&#xff0c;今天全单位崩溃。 ​ 提示许可15天内到期。之前大部分许可是到2021年1月1日的。 ​ 后续的版本许可都是永久的…

哪些企业需要用OV 证书?怎么获取OV证书?看这里

OV证书相当于DV证书而言&#xff0c;其安全等级高&#xff0c;兼容性强&#xff0c;稳定性好&#xff0c;那么哪些企业适用OV证书呢&#xff1f; 1 政府与公共服务网站 政府机构及提供公共服务的网站&#xff0c;必须确保数据的隐私和安全&#xff0c;有助于增强公众对在线服务…

台服dnf局域网搭建,学习用笔记

台服dnf局域网搭建 前置条件虚拟机初始化上传安装脚本以及其他文件至虚拟机密钥publickey.pem客户端配置如果IP地址填写有误&#xff0c;批量修改IP地址 前置条件 安装有vmvarecentos7.6镜像&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/centos-vault/7.6.1810/isos/x86…

拥有蝴蝶效应的爬虫如何进行防护

美国气象学家爱德华罗伦兹&#xff08;Edward N.Lorenz&#xff09;1963年在一篇提交纽约科学院的论文中分析了一个叫做蝴蝶效应的理论&#xff1a;“一个气象学家提及&#xff0c;如果这个理论被证明正确&#xff0c;一只海鸥扇动翅膀足以永远改变天气变化。”在以后的演讲和论…

输出正射图时,分辨率怎么填写整幅输出?

答&#xff1a;设置完输出路径、分辨率、坐标系后&#xff0c;会给图像宽高&#xff0c;根据最大值设置分幅尺寸就可以。 DasViewer是由大势智慧自主研发的免费的实景三维模型浏览器,采用多细节层次模型逐步自适应加载技术,让用户在极低的电脑配置下,也能流畅的加载较大规模实景…

红龙工业设备制造有限公司亮相2024杭州数字物流技术设备展

参展企业介绍 温州红龙工业设备制造有限公司成立于2015年11月。是中国先进的工业皮带设备研发制造和工业皮带整体解决方案运营服务商&#xff0c;现主营皮带接头机、皮带热压机、皮带接驳机、皮带打齿机、输送带打齿机、输送带分层级、输送带导条机、输送带裁切机、高频机等工业…

前端开发指导

前端开发指导 本文介绍了配置前端开发环境需要的软件、配置项等,指导如何开始进行UDM部门前端开发的全流程。本文以Windows系统下在Microsoft Virtual Studio Code中开发为基础。 一、综述 目标:零基础或者新员工依照此文档,能够完成开发环境的搭建及熟悉测试环境的搭建。…

陪诊陪护小程序基于ThinkPHP + FastAdmin + 微信小程序开发(源码搭建/上线/运营/售后/更新

支持多运营区&#xff0c;陪护师、推广者等完整闭环功能&#xff0c;快速搭建陪护业务平台。 消息通知&#xff1a;系统可以向用户发送订单状态变更、陪诊员信息更新等通知&#xff0c;确保用户及时了解相关信息&#xff0c;提高用户体验。 订单管理&#xff1a;患者可以查看自…

QT 小项目:登录注册账号和忘记密码(下一章实现远程登录)

一、环境搭建 参考上一章环境 二、项目工程目录 三、主要源程序如下&#xff1a; registeraccountwindow.cpp 窗口初始化&#xff1a; void registeraccountWindow::reginit() {//去掉&#xff1f;号this->setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButt…

《数据结构与算法之美》学习笔记一

前言&#xff1a;今天开始学习极客时间的课程《数据结构与算法之美》。为撒要学习这个&#xff1f;因为做力扣题太费劲了&#xff0c;自己的基础太差了&#xff01;所以要学习学习。开一个系列记录一下学习笔记。认真学吧&#xff0c;学有所获才不负韶华&#xff01;之前就学过…

【bug记录】Vue3 Vant UI 中 van-popup 不弹出

原因&#xff1a;语法使用错误&#xff0c;使用了 Vue 2 的语法 Vue3语法&#xff1a; Vue2语法&#xff1a;

JAVA IO/NIO 知识点总结

一、常见 IO 模型简介 1. 阻塞IO模型 最传统的一种IO模型&#xff0c;即在读写数据过程中会发生阻塞现象。当用户线程发出IO请求之后&#xff0c;内核会去查看数据是否就绪&#xff0c;如果没有就绪就会等待数据就绪&#xff0c;而用户线程就会处于阻塞状态&#xff0c;用户线…

企业微信创建应用(一)

登录到企业微信后台管理(https://work.weixin.qq.com/)进入自建应用(应用管理-应用-创建应用) 3.查看参数AgentId和 Secret 4.企业微信查看效果

致远M3 Session 敏感信息泄露漏洞复现

0x01 产品简介 M3移动办公是致远互联打造的一站式智能工作平台,提供全方位的企业移动业务管理,致力于构建以人为中心的智能化移动应用场景,促进人员工作积极性和创造力,提升企业效率和效能,是为企业量身定制的移动智慧协同平台。 0x02 漏洞概述 致远M3 server多个日志文…

我国吻合器市场规模不断扩大 国产化率有所增长

我国吻合器市场规模不断扩大 国产化率有所增长 吻合器是替代手工切除或缝合的一种医疗器械&#xff0c;其工作原理与订书机十分相似&#xff0c;可利用钛钉对组织进行离断或吻合。经过多年发展&#xff0c;吻合器种类逐渐增多&#xff0c;根据手术方式不同&#xff0c;吻合器大…

uni-app(二):本地插件使用(Android)

本地插件使用 项目创建等参考1.下载并引用本地插件2.注意插件配置3.制作自定义基座4.编写调用代码5.运行 项目创建等参考 https://lprosper.blog.csdn.net/article/details/138655526 1.下载并引用本地插件 2.注意插件配置 3.制作自定义基座 4.编写调用代码 <template>…

前端动画requestAnimationFrame

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画&#xff0c;并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数&#xff0c;该回调函数会在浏览器下一次重绘之前执行。 备注&#xff1a; 若你想在浏览器下次重绘…

从头理解transformer,注意力机制(上)

深入理解注意力机制和Transformer架构&#xff0c;及其在NLP和其他领域的突破。 要想理解transformer&#xff0c;先从编码器解码器结构开始理解 基于transformer发展起来的llm 右边&#xff1a;只有解码器&#xff0c;强项是生成内容 左边&#xff1a;只有编码器&#xff0…