网站运营技巧,百度小程序中心,cms网站下载,网站建设从入门pdf阅读导航 引言一、C异常的概念二、异常的使用1. 异常的抛出和捕获#xff08;1#xff09;throw#xff08;2#xff09;try-catch#xff08;3#xff09;catch(. . .)#xff08;4#xff09;异常的抛出和匹配原则#xff08;5#xff09;在函数调用链中异常栈展开… 阅读导航 引言一、C异常的概念二、异常的使用1. 异常的抛出和捕获1throw2try-catch3catch(. . .)4异常的抛出和匹配原则5在函数调用链中异常栈展开匹配原则 2. 异常的重新抛出3. 异常安全4. 异常规范 三、自定义异常体系1. 创建基类异常2. 创建派生异常3. 抛出异常4. 捕获和处理异常5. 自定义异常体系示例 四、C标准库的异常体系五、异常的优缺点1. C异常的优点2. C异常的缺点3. 总结 温馨提示 引言
在C编程中异常处理是一项重要的技术它允许我们更好地应对程序运行过程中可能出现的错误和异常情况。本文将介绍C中异常处理的基本概念、语法和最佳实践。我们将深入探讨try-catch块的使用方式以及如何抛出和捕获不同类型的异常。
希望本文能够帮助您更好地理解和运用C异常处理机制并为您的编程工作带来便利和效益。祝您阅读愉快
一、C异常的概念
C异常是一种用于处理程序运行时错误和异常情况的机制。当程序在执行过程中遇到无法正常处理的错误时可以通过抛出异常来中断当前的执行流程并将控制权转交给异常处理器。异常处理器可以捕获并处理异常从而使程序能够优雅地处理错误情况避免崩溃或数据丢失。异常可以表示各种不同类型的错误例如除以零、访问不存在的内存地址、文件打开失败等。
二、异常的使用
如果有一个块抛出一个异常捕获异常的方法会使用 try 和 catch 关键字。try 块中放置可能抛出异常的代码try 块中的代码被称为保护代码。
1. 异常的抛出和捕获
1throw
throw关键字用于抛出异常。当程序在执行过程中遇到无法正常处理的错误时可以通过throw语句抛出一个异常让异常处理器来捕获并处理这个异常。例如
if (count 0) {throw std::runtime_error(Divide by zero exception);
}在上面的例子中如果count等于0则会抛出一个std::runtime_error异常并附带异常信息“Divide by zero exception”。
2try-catch
try-catch块用于捕获和处理异常。try块用于包含可能引发异常的代码catch块则用于捕获和处理特定类型的异常。例如
try {// 可能引发异常的代码
} catch (std::exception e) {// 捕获std::exception及其派生类的异常std::cerr Exception caught: e.what() std::endl;
} catch (...) {// 捕获其他类型的异常std::cerr Unknown exception caught std::endl;
}在上面的例子中try块包含可能引发异常的代码catch块捕获std::exception及其派生类的异常并输出异常信息。如果没有合适的catch块来处理异常则会跳转到最近的catch(...)块。
3catch(. . .)
catch(...)块用于捕获任何类型的异常。如果程序抛出了一个未被其他catch块捕获的异常则会跳转到最近的catch(...)块并执行相关的异常处理代码。
4异常的抛出和匹配原则
异常是通过抛出对象而引发的该对象的类型决定了应该激活哪个catch的处理代码。被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。抛出异常对象后会生成一个异常对象的拷贝因为抛出的异常对象可能是一个临时对象所以会生成一个拷贝对象这个拷贝的临时对象会在被catch以后销毁。这里的处理类似于函数的传值返回catch(…)可以捕获任意类型的异常问题是不知道异常错误是什么。实际中抛出和捕获的匹配原则有个例外并不都是类型完全匹配可以抛出的派生类对象使用基类捕获这个在实际中非常实用。
5在函数调用链中异常栈展开匹配原则
首先检查throw本身是否在try块内部如果是再查找匹配的catch语句。如果有匹配的则调到catch的地方进行处理。没有匹配的catch则退出当前函数栈继续在调用函数的栈中进行查找匹配的catch。如果到达main函数的栈依旧没有匹配的则终止程序。找到匹配的catch子句并处理以后会继续沿着catch子句后面继续执行。
注意上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。所以实际中我们最后都要加一个catch(…)捕获任意类型的异常否则当有异常没捕获程序就会直接终止。
2. 异常的重新抛出
在C中异常的重新抛出是指在一个catch块中捕获到一个异常后再次将它抛出以便由更高层次的异常处理器来处理。这种重新抛出异常的操作可以在catch块中使用throw语句来实现。
⭕重新抛出异常的语法如下
try {// 可能引发异常的代码
} catch (ExceptionType1 e) {// 异常处理代码throw; // 重新抛出异常
} catch (ExceptionType2 e) {// 异常处理代码throw; // 重新抛出异常
}在上面的代码中当在catch块中捕获到ExceptionType1类型的异常时我们可以选择重新抛出该异常让更高层的异常处理器来处理。通过throw语句异常会被重新抛出并继续向上寻找匹配的catch块。
重新抛出异常的好处在于在某些情况下当前的catch块可能无法完全处理异常并希望由更上层的异常处理器来处理。这样可以将异常传递给更高层的代码进行处理而不是简单地在当前的catch块中忽略掉异常。
注意在重新抛出异常时可以选择不指定异常类型即使用throw;来重新抛出原始的异常。这样做的好处是可以保持异常的类型和信息不变使得更高层的异常处理器可以正常地捕获和处理异常。
3. 异常安全
在C中异常安全是指程序在抛出异常时仍然能够保证程序的正确性和资源的释放。异常安全是一个重要的编程概念因为在实际开发中程序运行过程中难免会遇到各种异常情况如内存不足、IO错误等而这些异常可能会导致程序崩溃或者产生未知的错误给程序的稳定性和可维护性带来很大的挑战。
为了提高程序的异常安全性我们需要采取一些措施主要包括以下几个方面 使用RAII技术
RAIIResource Acquisition Is Initialization是一种资源获取即初始化的技术通过C对象的构造函数获取资源利用析构函数自动释放资源。这样可以确保资源的正确释放即使在抛出异常的情况下也能够保证资源的正确释放。比如使用std::unique_ptr智能指针管理内存std::lock_guard管理锁等。关于RAII我们智能指针这篇文章有所讲解
不要泄漏资源
在程序执行过程中如果没有正确地释放资源就会导致资源泄漏进而影响程序的正确性和稳定性。因此在编写程序时要确保所有获取的资源都要正确释放以避免资源泄漏。
异常处理
在程序中如果遇到异常情况则需要正确地处理这些异常。一般来说我们可以使用try-catch语句块来捕获并处理异常确保程序在抛出异常时仍然能够保证程序的正确性和资源的释放。
4. 异常规范
异常规范Exception Specification是一种在C函数声明中指定函数可能抛出的异常类型的方法。异常规范可以告诉调用者该函数可能会抛出哪些异常以便调用者能够适当地处理这些异常。在C中有两种类型的异常规范动态异常规范和静态异常规范。
动态异常规范Dynamic Exception Specification
动态异常规范使用throw关键字在函数声明中列出函数可能抛出的异常类型。例如
void functionName() throw(ExceptionType1, ExceptionType2);上述代码表示函数functionName可能会抛出ExceptionType1和ExceptionType2类型的异常。如果函数抛出了未在异常规范中列出的异常就会调用std::unexpected()函数该函数默认会调用std::terminate()函数终止程序。
静态异常规范Static Exception Specification
静态异常规范使用noexcept关键字指示函数不会抛出任何异常。例如
void functionName() noexcept;上述代码表示函数functionName不会抛出任何异常。这样的函数被称为不抛出异常的函数编译器可以对其进行更多的优化。
在C11之后推荐使用noexcept来指示函数不会抛出异常而避免使用动态异常规范。同时C11引入了std::nothrow关键字用于在发生异常时返回一个null指针而不是抛出异常。
注意如果函数声明了静态异常规范但实际上抛出了异常程序会调用std::terminate()函数终止程序。
三、自定义异常体系
✅在C中可以通过自定义异常类来创建自己的异常体系。自定义异常体系可以用于对不同类型的异常进行分类和处理。在实际的程序中都会定义一套继承的规范体系。这样大家抛出的都是继承的派生类对象捕获一个基类就可以了。 ⭕下面是创建自定义异常体系的步骤
1. 创建基类异常
创建一个基类异常作为自定义异常体系的根。这个基类异常可以是std::exception的子类或者直接继承自std::exception。例如
class MyException : public std::exception {
public:virtual const char* what() const throw() {return My exception occurred;}
};在基类异常中通常会重写what()函数该函数返回异常的描述信息。
2. 创建派生异常
根据需要可以创建多个派生异常类用于表示不同类型的异常。这些派生异常类可以继承自基类异常或者其他已有的异常类。例如
class OutOfRangeException : public MyException {
public:virtual const char* what() const throw() {return Out of range exception;}
};class NullPointerException : public MyException {
public:virtual const char* what() const throw() {return Null pointer exception;}
};在派生异常类中也可以重写what()函数以提供特定异常的描述信息。
3. 抛出异常
在程序中遇到异常情况时可以使用throw语句抛出自定义的异常对象。例如
void myFunction() {throw OutOfRangeException();
}以上代码示例在myFunction()函数中抛出了一个OutOfRangeException异常对象。
4. 捕获和处理异常
在调用可能引发异常的代码时可以使用try-catch语句块捕获并处理异常。例如
try {myFunction();
} catch (MyException ex) {std::cout Caught exception: ex.what() std::endl;
}以上代码示例在调用myFunction()时捕获并处理了MyException及其派生异常的对象。
自定义异常体系可以根据具体需求进行扩展和细化以提供更好的异常分类和处理能力。通过创建不同类型的异常类可以更好地组织和处理程序中的异常情况。
5. 自定义异常体系示例
#include iostream
#include string
#include ctime
#include chrono
#include threadusing namespace std;// 自定义异常基类
class Exception
{
public:// 构造函数接收错误信息和错误码作为参数Exception(const string errmsg, int id): _errmsg(errmsg), _id(id){}// 返回异常信息的函数virtual string what() const{return _errmsg;}protected:string _errmsg; // 异常信息int _id; // 异常码
};// 派生自Exception的SqlException异常类
class SqlException : public Exception
{
public:// 构造函数接收错误信息、错误码和SQL语句作为参数SqlException(const string errmsg, int id, const string sql): Exception(errmsg, id), _sql(sql){}// 返回异常信息的函数virtual string what() const{string str SqlException:;str _errmsg;str -;str _sql;return str;}private:const string _sql; // SQL语句
};// 派生自Exception的CacheException异常类
class CacheException : public Exception
{
public:// 构造函数接收错误信息和错误码作为参数CacheException(const string errmsg, int id): Exception(errmsg, id){}// 返回异常信息的函数virtual string what() const{string str CacheException:;str _errmsg;return str;}
};// 派生自Exception的HttpServerException异常类
class HttpServerException : public Exception
{
public:// 构造函数接收错误信息、错误码和请求类型作为参数HttpServerException(const string errmsg, int id, const string type): Exception(errmsg, id), _type(type){}// 返回异常信息的函数virtual string what() const{string str HttpServerException:;str _type;str :;str _errmsg;return str;}private:const string _type; // 请求类型
};// SQL管理函数
void SQLMgr()
{srand(time(0));if (rand() % 7 0){// 如果随机数满足条件抛出SqlException异常throw SqlException(权限不足, 100, select * from name 张三);}
}// 缓存管理函数
void CacheMgr()
{srand(time(0));if (rand() % 5 0){// 如果随机数满足条件抛出CacheException异常throw CacheException(权限不足, 100);}else if (rand() % 6 0){// 如果随机数满足条件抛出CacheException异常throw CacheException(数据不存在, 101);}SQLMgr();
}// HTTP服务器函数
void HttpServer()
{// ...srand(time(0));if (rand() % 3 0){// 如果随机数满足条件抛出HttpServerException异常throw HttpServerException(请求资源不存在, 100, get);}else if (rand() % 4 0){// 如果随机数满足条件抛出HttpServerException异常throw HttpServerException(权限不足, 101, post);}CacheMgr();
}int main()
{while (1){this_thread::sleep_for(chrono::seconds(1));try {// 调用HttpServer函数可能会抛出异常HttpServer();}catch (const Exception e) {// 捕获Exception及其派生类的异常对象cout e.what() endl; // 输出异常信息}catch (...) {// 捕获其他类型的异常cout Unkown Exception endl;}}return 0;
}这是一个基于C的自定义异常体系。在该代码中自定义了三个异常类分别是SqlException、CacheException和HttpServerException这些异常类都派生自基类Exception。通过自定义异常类可以更准确地捕获异常同时也可以对不同类型的异常进行不同的处理。
在代码中SqlException用于处理SQL语句相关的异常CacheException用于处理缓存相关的异常HttpServerException用于处理HTTP服务器相关的异常。每个异常类都有一个构造函数用于初始化异常信息和错误码等参数。在捕获异常时可以根据异常类型来选择相应的处理方式比如输出错误信息、记录日志、重试操作等等。
四、C标准库的异常体系
⭕C 提供了一系列标准的异常定义在 中我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的如下图所示 下表是对上面层次结构中出现的每个异常的说明
异常类型描述std::exception所有异常类的基类std::logic_error程序逻辑错误std::invalid_argument无效参数std::domain_error域错误std::length_error长度错误std::out_of_range超出范围std::future_error异步执行错误std::runtime_error运行时错误std::range_error范围错误std::overflow_error溢出错误std::underflow_error下溢错误std::regex_error正则表达式错误std::bad_alloc内存分配错误std::bad_cast类型转换错误std::bad_exception未捕获的异常std::bad_function_call错误的函数调用std::bad_typeidtypeid操作错误std::bad_weak_ptrweak_ptr错误std::ios_base::failureI/O流错误std::system_error系统调用错误std::filesystem::filesystem_error文件系统错误std::experimental::filesystem::filesystem_error实验性文件系统错误
五、异常的优缺点
异常是一种处理程序运行时错误的机制它提供了一种在程序中处理异常情况的方式。以下是异常的优点和缺点
1. C异常的优点
异常对象定义好了相比错误码的方式可以清晰准确的展示出错误的各种信息甚至可以包含堆栈调用的信息这样可以帮助更好的定位程序的bug。异常可以提高代码的可读性和可维护性将错误处理代码与正常逻辑分离开来使代码更加清晰易懂。异常可以通过层级结构进行处理允许异常在不同的层次上被捕获和处理从而提高了程序的灵活性和扩展性。
2. C异常的缺点
异常可能会导致性能问题因为在发生异常时需要执行一些额外的操作如调用析构函数、回收内存等这些操作会增加程序的开销。异常可能会影响代码的可移植性因为异常处理机制在不同的编译器和操作系统上可能有所不同需要针对不同的平台进行调整。异常可能会导致程序的安全性问题因为异常可以破坏程序的状态可能会被恶意利用。此外异常也可能会隐藏错误使得程序的调试和测试变得更加困难。异常会导致程序的执行流乱跳并且非常的混乱并且是运行时出错抛异常就会乱跳。这会导致我们跟踪调试时以及分析程序时比较困难。
3. 总结
综上所述异常总体而言利大于弊所以工程中我们还是鼓励使用异常的。在使用异常时我们应该权衡利弊根据实际情况选择合适的处理方式保证程序的健壮性和安全性。
温馨提示
感谢您对博主文章的关注与支持另外我计划在未来的更新中持续探讨与本文相关的内容会为您带来更多关于C以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新不要错过任何精彩内容
再次感谢您的支持和关注。期待与您建立更紧密的互动共同探索C、算法和编程的奥秘。祝您生活愉快排便顺畅