专门做音箱的网站,个人养老保险网上怎么缴费,做网站前台需要学什么 后台,php教育网站开发前言#xff1a;
本期#xff0c;我将给大家讲解的是有关 异常处理 的相关知识#xff01; 目录
#xff08;一#xff09;C语言传统的处理错误的方式
#xff08;二#xff09;C异常概念
#xff08;三#xff09;异常的使用
1、异常的抛出和捕获
1️⃣ 异常的…前言
本期我将给大家讲解的是有关 异常处理 的相关知识 目录
一C语言传统的处理错误的方式
二C异常概念
三异常的使用
1、异常的抛出和捕获
1️⃣ 异常的抛出和匹配原则
2️⃣ 在函数调用链中异常栈展开匹配原则
2、异常的重新抛出
3、异常安全
4、异常规范
四C标准库的异常体系
五异常的优缺点
总结 一C语言传统的处理错误的方式
首先我们回顾一下C语言处理异常的相关方式 终止程序如assert缺陷用户难以接受。如发生内存错误除0错误时就会终止程序。
下面是使用宏进行异常处理的简单代码描述
#include stdio.h
#include assert.hint divide(int num1, int num2)
{assert(num2 ! 0); // 断言num2不等于0return num1 / num2;
}int main()
{int result divide(10, 0);// 如果编译时定义了NDEBUG宏assert会被禁用否则会触发异常并终止程序执行return 0;
}
【解释说明】
1、在上述代码中函数用于实现两个整数的除法运算。通过在函数内使用宏可以进行条件判断确保除数不为零。如果为零会触发异常终止程序的执行。
2、在函数中调用函数并传递除数为0的情况。如果编译时未定义宏即未启用调试模式会触发异常并终止程序执行。如果定义了宏则会被禁用不会触发异常程序会继续执行后续的代码。
【输出展示】 返回错误码缺陷需要程序员自己去查找对应的错误。如系统的很多库的接口函数都是通过把错误码放到errno中表示错误 下面是使用返回错误码的方式处理异常的示例代码
#include stdio.h
int divide(int num1, int num2, int* res)
{if (num2 0) {return -1; // 返回错误码 -1 表示除数为零的异常情况}*res num1 / num2;return 0; // 返回 0 表示成功
}int main()
{int num1 10, num2 0, res;int num divide(num1, num2, res);if (num ! 0){printf(Error: Divide by zero\n);// 处理错误的逻辑}else {printf(Result: %d\n, res);// 处理正常情况的逻辑}return 0;
} 【解释说明】
在函数中调用函数并传递除数为0的情况。函数返回的错误码被存储在变量中通过判断的值可以确定是正常情况还是异常情况。如果不等于0则表示发生了异常可以根据具体情况进行错误处理。如果返回的错误码为0表示除法运算成功可以通过变量获得计算结果执行相应的正常处理逻辑。 【输出展示】 实际中C语言基本都是使用返回错误码的方式处理错误部分情况下使用终止程序处理非常严重的错误。 二C异常概念 在C中异常Exception是一种用于处理程序运行时错误的机制。异常提供了一种跳出正常程序流程的方式将错误信息传递到适当的处理程序进行处理。 以下是关于C异常的一些概念 异常抛出当发生异常情况时可以使用throw 语句将异常抛出。throw 语句通常包含一个异常对象该对象可以是基本类型、类对象或指针 异常捕获异常被抛出后程序可以使用try-catch语句块来捕获并处理异常。try 块包含可能发生异常的代码而catch块用于捕获和处理异常 异常处理程序catch 块是用于处理异常的代码块。在catch 块中可以根据抛出的异常类型来执行相应的处理逻辑。可以有多个 catch 块按照顺序逐个匹配异常类型并执行匹配的处理逻辑。 如果有一个块抛出一个异常捕获异常的方法会使用 try 和 catch 关键字。try 块中放置可能抛 出异常的代码try 块中的代码被称为保护代码。
使用 try/catch 语句的语法如下所示
try
{// 保护的标识代码
}catch( ExceptionName e1 )
{// catch 块
}catch( ExceptionName e2 )
{// catch 块
}catch( ExceptionName eN )
{// catch 块
} 【小结】
通过合理使用异常处理机制在程序中对错误进行捕获和处理可以增加程序的健壮性和可维护性合适的异常处理可以使代码更清晰、可读性更好并且可以更好地处理异常情况提高程序的容错能力。 三异常的使用 1、异常的抛出和捕获 在C中异常的抛出和匹配原则遵循以下几个基本原则 1️⃣ 异常的抛出和匹配原则 异常抛出 当程序发生异常情况时可以使用throw语句将异常抛出。throw语句通常包含一个异常对象该对象可以是基本类型、类对象或指针。 异常匹配 异常的匹配是指根据抛出的异常类型来选择处理该异常的catch块。C的异常处理机制会按照顺序匹配try块中catch块的类型找到能处理该异常类型的catch块。 异常类型匹配和继承关系 C允许异常类型形成继承关系即派生类的异常对象可以被基类的catch块捕获。如果异常类型存在继承关系派生类的catch块应该放在基类的catch块之前否则派生类的catch块将无法执行。 最匹配的异常处理 C异常处理机制会选择最匹配的catch块来处理抛出的异常。最匹配的catch块是指能够处理抛出的异常类型或其基类类型的catch块即异常类型匹配的最接近情况。 异常未匹配的处理 如果在try块中抛出了异常但没有找到匹配的catch块处理该异常异常将传递到更高层的调用栈。如果异常一直没有被匹配的catch块处理最终导致程序终止执行并可能输出异常信息。
【注意事项】
异常的抛出和匹配原则是按照顺序匹配catch块来选择处理异常因此在catch块的顺序布置上要谨慎通常应从具体的异常类型开始然后再向基类类型进行匹配以确保异常可以被正确处理并执行相应的异常处理逻辑。 2️⃣ 在函数调用链中异常栈展开匹配原则 在函数调用链中异常栈展开匹配原则主要指定了如何匹配异常类型并选择正确的异常处理代码。当异常发生时C运行时系统会从当前执行的函数开始逐级检查调用栈中的函数调用以查找与抛出的异常类型匹配的catch块。 以下是异常栈展开和匹配的原则 检查当前函数的try块 如果当前函数包含try块运行时系统将查找匹配的catch块。 检查当前函数的catch块 如果当前函数包含与抛出的异常类型匹配的catch块那么该catch块将被执行。如果找到多个匹配的catch块将选择最接近的最近的catch块来处理异常。 如果当前函数没有匹配的catch块 异常栈展开到上一级调用函数。重复步骤 1 和步骤 2直到找到匹配的catch块或达到调用栈的最顶层。 如果在整个调用栈中没有找到匹配的catch块 程序的执行将终止并调用标准库函数terminate()来终止程序。 关于异常栈展开和匹配的重要注意事项
异常匹配时按照栈展开的顺序进行而不是抛出异常的顺序。派生类的异常对象可以被基类的catch块捕获因此在派生类的catch块之前应放置基类的catch块。如果在某个函数中抛出的异常没有匹配的catch块处理异常会一直沿着调用栈向上传递直到找到匹配的catch块或终止程序。异常的栈展开会跨越函数和线程边界因此在多线程程序中也适用这些匹配原则。 例如以下示例 接下来通过代码来具体的理解
double Division(int a, int b)
{if (b 0)throw Division by zero condition!;elsereturn ((double)a / (double)b);
}void Func()
{int len, time;cin len time;cout Division(len, time) endl;
}int main()
{try {Func();}catch (const char* errmsg) {cout errmsg endl;}catch (...) {cout unknown exception endl;}return 0;
}
输出展示 【解释说明】
通过异常处理机制来捕获并处理可能出现的除以零异常。当除以零发生时会抛出一个字符串常量异常并被catch (const char* errmsg)块捕获如果出现其他类型的异常则被catch (...)块捕获并执行相应的处理逻辑。 2、异常的重新抛出 在C中异常的重新抛出允许在catch块内部对捕获的异常进行处理并将其重新抛出以便让更高层的异常处理代码进一步处理该异常。可以使用throw语句将异常重新抛出。 以下是一个使用异常重新抛出的示例代码
double Division(int a, int b)
{// 当b 0时抛出异常if (b 0){throw Division by zero condition!;}return (double)a / (double)b;
}
void Func()
{// 这里可以看到如果发生除0错误抛出异常另外下面的array没有得到释放。// 所以这里捕获异常后并不处理异常异常还是交给外面处理这里捕获了再// 重新抛出去。int* array new int[10];try {int len, time;cin len time;cout Division(len, time) endl;}catch (...){cout delete [] array endl;delete[] array;throw;}// ...cout delete [] array endl;delete[] array;
}
int main()
{try{Func();}catch (const char* errmsg){cout errmsg endl;}return 0;
}
【解释说明】
在Func函数中如果除以零的异常发生异常将被捕获并输出删除array的信息。然后array会被释放使用delete[]并使用throw语句重新抛出异常。这样异常会传递到更高层级的代码中。在main函数中异常被最外层的catch块捕获并输出异常信息。通过在Func函数中重新抛出异常并在捕获异常之前及其后释放array可以确保在异常传递给更高层级之前已经释放了相关的资源。
【小结】
通过异常的重新抛出可以在异常被捕获的地方对异常进行适当处理并在更高层级的代码中继续处理相同的异常或进行其他操作。这种机制提供了灵活性和错误的向上传递。 3、异常安全
构造函数完成对象的构造和初始化最好不要在构造函数中抛出异常否则可能导致对象不完整或没有完全初始化析构函数主要完成资源的清理最好不要在析构函数内抛出异常否则可能导致资源泄漏(内存泄漏、句柄未关闭等)C中异常经常会导致资源泄漏的问题比如在new和delete中抛出了异常导致内存泄漏在lock和unlock之间抛出了异常导致死锁C经常使用RAII来解决以上问题关于RAII我们智能指针这节进行讲解 4、异常规范 在C中异常规范是一种在函数声明中指定函数可能抛出的异常的方式。异常规范可以作为函数的一部分用于标识函数可能引发的异常类型。具体来说异常规范指定了函数可抛出的异常类型列表。 在C98\03 中异常规范使用了throw()声明。例如
void foo() throw(int, std::exception);
【解释说明】
上述代码表示函数foo可能抛出int类型和exception类型的异常如果函数抛出了未在异常规范中列出的其他异常类型程序会调用unexpected函数默认情况下会导致terminate被调用终止程序。
更多示例如下图所示
// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(ABCD);// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw(); 在C11开始引入了更为灵活和安全的异常处理机制即异常规范的替代方案异常说明。异常说明使用noexcept关键字指定函数是否允许抛出异常。 使用noexcept关键字的函数可以被称为“noexcept函数”或“不抛异常函数”。它们在以下方面有一些重要的用途和优点
优化性能编译器可以基于对noexcept的显式承诺做出一些优化异常传播有助于避免异常传播到不应该处理异常的上下文中
下面是一些使用noexcept的示例
void myFunction() noexcept {// 函数体不会抛出异常
}void anotherFunction() {// 函数体可能会抛出异常
}void myFunction2() noexcept(true) {// 与上面的 myFunction 等效不会抛出异常
}void myFunction3() noexcept(false) {// 与 anotherFunction 等效可能会抛出异常
}//不会抛出异常
thread (thread x) noexcept;
【注意事项】
在C11中noexcept关键字可以作为函数类型的一部分标志着函数是否抛出异常在C17之后noexcept支持函数表达式以动态地决定是否抛出异常。这使得异常规范在一些特定的情况下更加灵活和动态。 四C标准库的异常体系
C 提供了一系列标准的异常定义在 中我们可以在程序中使用这些标准的异常。它们是以父 子类层次结构组织起来的如下所示 说明实际中我们可以可以去继承exception类实现自己的异常类。但是实际中很多公司像上面一样自己定义一套异常继承体系。因为C标准库设计的不够好用。
int main()
{try {vectorint v(10, 5);// 这里如果系统内存不够也会抛异常v.reserve(1000000000);// 这里越界会抛异常v.at(10) 100;}catch (const exception e) // 这里捕获父类对象就可以{cout e.what() endl;}catch (...){cout Unkown Exception endl;}return 0;
} 五异常的优缺点 C异常的优点
1. 异常对象定义好了相比错误码的方式可以清晰准确的展示出错误的各种信息甚至可以包含堆栈调用的信息这样可以帮助更好的定位程序的bug。2. 返回错误码的传统方式有个很大的问题就是在函数调用链中深层的函数返回了错误那么我们得层层返回错误最外层才能拿到错误具体看下面的详细解释。
// 1.下面这段伪代码我们可以看到ConnnectSql中出错了先返回给ServerStart
ServerStart再返回给main函数main函数再针对问题处理具体的错误。
// 2.如果是异常体系不管是ConnnectSql还是ServerStart及调用函数出错都不用检查因
为抛出的异常异常会直接跳到main函数中catch捕获的地方main函数直接处理错误。
int ConnnectSql()
{// 用户名密码错误if (...)return 1;// 权限不足if (...)return 2;
}
int ServerStart() {if (int ret ConnnectSql() 0)return ret;int fd socket()iffd 0return errno;
}
int main()
{if (ServerStart() 0)...return 0;
}
3. 很多的第三方库都包含异常比如boost、gtest、gmock等等常用的库那么我们使用它们也需要使用异常。4. 部分函数使用异常更好处理比如构造函数没有返回值不方便使用错误码方式处理。比如T operator这样的函数如果pos越界了只能使用异常或者终止程序处理没办法通过返回值表示错误。 C异常的缺点
1. 异常会导致程序的执行流乱跳并且非常的混乱并且是运行时出错抛异常就会乱跳。这会导致我们跟踪调试时以及分析程序时比较困难。2. 异常会有一些性能的开销。当然在现代硬件速度很快的情况下这个影响基本忽略不计。3. C没有垃圾回收机制资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常安全问题。这个需要使用RAII来处理资源的管理问题。学习成本较高。4. C标准库的异常体系定义得不好导致大家各自定义各自的异常体系非常的混乱。5. 异常尽量规范使用否则后果不堪设想随意抛异常外层捕获的用户苦不堪言。所以异常规范有两点一、抛出异常类型都继承自一个基类。二、函数是否抛异常、抛什么异常都使用 func throw();的方式规范化。 总结 以上便是关于 c11 有关异常的全部知识。接下来简单的回顾下本文 异常总体而言利大于弊所以工程中我们还是鼓励使用异常的另外OO的语言基本都是用异常处理错误这也可以看出这是大势所趋。 到此关于本篇便到此为止了。感谢大家的观看与支持