您现在的位置是:首页 >其他 >「C/C++」C/C++异常处理网站首页其他

「C/C++」C/C++异常处理

何曾参静谧 2023-06-07 16:00:02
简介「C/C++」C/C++异常处理

在这里插入图片描述

博客主页:何曾参静谧的博客
文章专栏:「C/C++」C/C++学习


相关术语

异常(Exception)处理:对程序运行时产生的异常进行处理。常见的异常如:计算异常、格式异常、内存不足异常、文件异常等。

一、C语言中的异常处理

C语言本身并没有提供内置的异常处理机制,但我们可以通过一些技巧来处理异常情况。
一般有两种方式进行处理:

  • 方法一:是通过返回值来传递错误信息
    例如在函数执行失败时返回一个特定的错误码,调用者可以根据返回值来判断函数是否执行成功并采取相应的处理方式。if()else()
  • 方法二:使用标准库函数对异常进行处理(不推荐)
    不推荐这种方法,暴力跳转导致代码可读性不高。
    assert()函数:用于在程序中检查一个条件是否成立,如果不成立,则中断程序并输出错误信息。
    setjmp()longjmp()函数:用于实现非局部跳转,在出现错误时跳转到事先设定的跳转点。
  • 方法三:是使用全局变量来记录错误信息(不推荐)
    这种方法不够安全,容易被其他代码改变这个全局变量的值,但是在一些简单的程序中可以使用。

1.返回值来传递错误信息

以下例子为一个除法的函数,当除数为0返回结果为-1

int divide(int a, int b, int* result) {
	const double eps = 1e-9;
    if (fabs(b) < eps) { // 除数为0,发生错误
        return 0; // 返回错误码0
    } else {
        *result = a / b; // 计算商
        return 1; // 返回成功码1
    }
}

2.使用标准库函数对异常进行处理(不推荐)

使用assert()函数进行处理

#include <assert.h>
#include <stdio.h>

int main() {
    int a = 10, b = 0;
    assert(b != 0); // 如果b等于0,程序会中断执行,并输出错误信息
    int result = a / b;
    printf("%d / %d = %d", a, b, result);
    return 0;
}

使用setjmp()longjmp()函数进行处理

  • int setjmp(jmp_buf env)
    将当前上下文保持在jmp_buf结构体中
  • void longjmp(jmp_buf env, int val)
    jmp_buf结构体中恢复setjmp()保存的上下文
    最终从setjmp函数调用点返回,返回值为val
#include <setjmp.h>
#include <math.h>
#include <stdio.h>

static jmp_buf env;

double divide(int a, int b) {
	const double eps = 1e-9;
	double ret = 0;
    if (fabs(b) < eps) { // 除数为0,发生错误
        longjmp(env,1);
    } else {
        ret = a / b;
    }
    return ret;
}

int main(int argc, char *argv[]){
	if(setjmp(env) == 0){
		double ret = divide(1,0);
		printf("r=%d", ret);
	}
	else{
		printf("除数为0");
	}
}

3.使用全局变量来记录错误信息(不推荐)

在多线程的环境下,全局变量可能会引起线程安全问题,因此在使用全局变量的时候需要注意线程同步问题。

二、C++中的异常处理

C++中的异常处理是一种程序控制流程的机制,用于处理程序运行时出现的异常情况,例如除以零、非法输入等等。通过使用异常处理,可以使程序在出现异常时不退出,而是能够优雅地处理异常,从而保证程序的可靠性稳定性

1.try{}catch(){}异常处理

C++中的异常处理机制主要包含以下几个关键字:

  • try:
    用于包含可能会抛出异常的代码块,对于可能抛出异常的代码需要放在try语句块中进行封装。
  • catch:
    用于捕获并处理由try块中抛出的异常,例如输出错误信息、恢复程序状态等等。可以有多个 catch 语句处理不同类型的异常。
    注意事项:
    1. 任何异常只会被catch捕捉一次。
    2. catch(...)只能在最后进行接受。
    3. 参数是严格匹配,不进行任何数据转换。
  • throw:
    用于在代码块中抛出异常,当程序在代码块中遇到了一个异常,就可以把这个异常抛出,交给try语句块之外的catch语句块进行处理。
//以下是示例代码:
#include <iostream>
#include <string>
#include <cmath>

using namespace std;

double divide(int a, int b) {
	const double eps = 1e-9;
	double ret = 0;
    if (fabs(b) < eps) { // 除数为0,发生错误
        throw 0;
    } else {
        ret = a / b;
    }
    return ret;
}

int main(int argc, char *argv[]){
	try{
		double ret = divide(1,0);
		cout << "r=" << ret << endl;
	}
	catch(...){
		cout << "除数为0" << endl;
	}
}

2.C++标准的异常

需要包含标准的头文件:#inlcude <stdexcept>

异常名描述举例
标准异常这些异常是 C++ 标准定义的异常std::runtime_error、std::logic_error、std::out_of_range
输入输出异常这些异常通常都与输入输出操作相关std::ios_base::failure 和 std::bad_cast
内存异常这些异常通常与内存分配和释放操作相关std::bad_alloc 和 std::bad_array_new_length
数值计算异常这些异常通常与数值计算相关std::overflow_error、std::underflow_error
用户自定义异常这些异常由程序员自己定义继承std::exception
#include <iostream>
#include <stdexcept>

double divide(double a, double b) {
	const double eps = 1e-9;
	double ret = 0;
    if (fabs(b) < eps) { // 除数为0,发生错误
          throw std::invalid_argument("除数为0,发生错误");
    } else {
        ret = a / b;
    }
    return ret;
}

int main(){
    double a = 10, b = 0;
    try{
        int result = divide(a, b);
        std::cout << "结果: " << result << std::endl;
    }
    catch (std::exception& e){
        std::cerr << "异常捕捉: " << e.what() << std::endl;
    }
    return 0;
}

3.异常的重新解析(工程开发中比较实用)

常用于工程开发:当调用第三方库函数时,它本身已经有自己的异常处理机制但我们想用自己的异常处理时使用异常的重新解析。

//以下是一个示例代码,演示了如何对 `open` 函数进行异常转换:
#include <iostream>
#include <stdexcept>    // 包含 std::exception 等标准异常类
#include <fstream>      // 包含 open 函数
#include <string>

class FileOpenFailed : public std::exception
{
public:
    FileOpenFailed(const std::string& fileName) : m_fileName(fileName) {}

    virtual const char* what() const noexcept{
        return ("Failed to open file: " + m_fileName).c_str();
    }

private:
    std::string m_fileName;
};

void openFile(const std::string& fileName)
{
    std::ifstream file;
    file.exceptions(std::ifstream::failbit);

    try{
        file.open(fileName);
    }catch(const std::ifstream::failure& e){// 捕获 std::ifstream::failure 异常
        throw FileOpenFailed(fileName);     // 转换为我们自己定义的异常并抛出
    }
    // 执行文件操作...
}

int main(){
    try{
        openFile("nonexistent.txt");
    }catch(const std::exception& e){// 捕获我们自己定义的异常
        std::cerr << e.what() << '';
        return -1;
    }
    // ...
    return 0;
}

4.自定义异常类

#include <iostream>
#include <exception>
#include <string>

class MyException : public std::exception
{
public:
    MyException(const char* message) : msg(message) {}
    const char* what() const throw() { return msg.c_str(); }
private:
    std::string msg;
};

void divide(int x, int y){
    if (y == 0) {
        throw MyException("Division by zero");
    }
    std::cout << "Result: " << x / y << std::endl;
}

int main(){
    try {
        divide(10, 2);
        divide(10, 0);
    } catch (MyException& e) {
        std::cout << "Exception caught: " << e.what() << std::endl;
    }
    
    return 0;
}

在这里插入图片描述

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。