房地产网站 模板,wordpress+手工网站,建湖县建设局网站,wordpress 4 手册 chm#x1f308; 个人主页#xff1a;谁在夜里看海. #x1f525; 个人专栏#xff1a;《C系列》《Linux系列》 ⛰️ 天高地阔#xff0c;欲往观之。 目录
引言
1.编译器可以处理的错误
2.编译器不能处理的错误
3.传统的错误处理机制
assert终止程序
返回错误码
一、… 个人主页谁在夜里看海. 个人专栏《C系列》《Linux系列》 ⛰️ 天高地阔欲往观之。 目录
引言
1.编译器可以处理的错误
2.编译器不能处理的错误
3.传统的错误处理机制
assert终止程序
返回错误码
一、异常的概念
直观的例子
二、异常的使用
1.异常的抛出和捕获
抛出异常
捕获异常
处理异常
2.异常的重新抛出
三、异常的安全与规范
1.异常安全
2.异常规范
3.自定义异常体系
四、异常的优缺点 引言
我们在编写程序的时候不可避免地会造成一些错误有些错误是编译器可以帮我们找出并纠正的而有些错误则需要我们自己自行处理。
1.编译器可以处理的错误
一般是一些静态的语义和语法错误下面列举一些常见的错误
// 1.语法错误
int a 0 // 缺少封号// 2.类型错误
int b hello; // 类型不匹配// 3.未定义标识符
int result unknownFunc(); // 未定义的函数// 4.作用域错误
if(1){int x 10;
}
cout x; // 不在当前作用域// ......
2.编译器不能处理的错误
编译器无法检测和处理的错误主要是程序运行时才会发生的动态错误例如下面几种
// 1.运行时错误除零、非法访问内存、堆栈溢出
int a 10;
int b 0;
int c a / b; // 运行时的除零错误// 2.逻辑错误逻辑错误是在代码设计或实现上出现的错误但从语法和类型上都是合法的。
// 逻辑错误只能通过调试和测试来发现。例如意图将数字从1到10累加但写错了循环条件
int sum 0;
for (int i 1; i 10; i) {sum - i; // 应该是 sum i
}// 3.资源管理错误包括内存泄露等问题编译器无法检测
int* ptr new int[10]; // 没有 delete[] ptr;// ......总上所述编译器负责静态检测只能检查代码中明显的语义和语法错误对于运行时的错误处理则需要我们设置适当的错误处理机制。
3.传统的错误处理机制
assert终止程序
assert是C和C中的一个宏用于在程序中检查条件是否满足如果条件不满足assert会报错并终止程序可以在错误发生的地方及时发现问题
int operation()
{int a 0;int b 0;cin a b; // 输入 3 0// assert判错assert(b); // 此时发现错误程序终止return a / b;
}int main()
{operation();return 0;
} 弊端
在调试过程中assert还是很有用的但是在发布版本Release模式下会降低性能因此会将其禁用。通过定义宏 NDEBUG 禁用 assert
#define NDEBUG
#include cassert而且在一些情况下使用assert终止程序会发生很严重的错误
实际上C语言基本是使用返回错误码的方式处理错误
返回错误码
在C语言中函数通常返回一个整数表示执行的结果 返回0表示执行成功 返回非零值整数或负数表示不同的错误类型根据错误码调用者可以判断错误类型 常见的错误码有以下类型
标准错误码使用标准库中定义的宏如 EXIT_SUCCESS 和 EXIT_FAILURE
#include stdlib.h
#include stdio.hint divide(int a, int b) {if (b 0) {return EXIT_FAILURE; // 返回标准错误码表示失败}printf(Result: %d\n, a / b);return EXIT_SUCCESS; // 返回标准错误码表示成功
}int main() {if (divide(10, 0) EXIT_FAILURE) {printf(Error: Division by zero.\n);}return 0;
}自定义错误码为程序中的不同错误类型定义特定的错误码。
#include stdio.h#define SUCCESS 0
#define ERR_DIVISION_BY_ZERO -1
#define ERR_INVALID_INPUT -2int divide(int a, int b, int* result) {if (b 0) {return ERR_DIVISION_BY_ZERO; // 返回自定义错误码}*result a / b;return SUCCESS; // 返回成功码
}int main() {int result;int status divide(10, 0, result);if (status ERR_DIVISION_BY_ZERO) {printf(Error: Division by zero.\n);} else if (status SUCCESS) {printf(Result: %d\n, result);}return 0;
}弊端
每次调用函数都要检查返回值如果嵌套调用较多会造成大量错误检查代码降低代码可读性我们需要一种更简洁更灵活的错误管理机制它就是C异常处理
一、异常的概念
异常是指程序运行时发生的、使程序无法正确执行的错误或异常情况而异常处理机制是一种处理异常的手段它允许程序在遇到问题时转移到异常处理逻辑而不是直接崩溃。
直观的例子 对异常不处理
int main()
{int a 0;int b 0;cin a b; // 输入 3 0cout a / b endl; // 发生除零异常程序中断return 0;
} 使用assert处理
int main()
{int a 0;int b 0;cin a b;// assert处理assert(b);cout a / b endl;return 0;
} 使用异常处理机制
int main()
{int a 0;int b 0;cin a b;// 异常处理机制try{if (b 0){throw The dividend is zero;}}catch (const char* ret){cout ret endl;return 0; // 遇到除零 结束程序}cout a / b endl;return 0;
} 我们可以看到相比于assert异常处理可以更直观地显示错误信息而不是让程序崩溃或无反应
下面介绍异常的使用方法
二、异常的使用
1.异常的抛出和捕获
抛出异常
当程序遇到无法正常处理的问题或错误情况时使用 throw 语句抛出异常。抛出的异常对象可以是一个具体的值如整数、字符串或特定的异常类实例 int a 0;int b 0;cin a b;// 异常处理机制try{if (b 0){throw The dividend is zero;}}
在这里如果b 0除零错误则抛出一个 char* 类型的错误信息提示“除零错误”。
捕获异常
当抛出异常后程序会自动寻找最接近的 catch 块来捕获这个异常。在 catch 块中可以编写处理代码以应对错误。多个 catch 块可以处理不同类型的异常。
int main()
{int a 0;int b 0;while (1){cin a b;// 异常处理判错try {if (b 0) {string info { The dividend is negative }; // 除负数假定为错误throw info;}if (b 0){throw The dividend is zero; // 除零错误}}catch (const string info) { // 捕获string类型的异常cout info endl;}catch (const char* info){ // 捕获char*类型的异常cout info endl;}}return 0;
} 在 try 块中调用 divide 函数如果发生异常程序会跳转到相应的 catch 块输出捕获到的异常信息。
处理异常
在 catch 块中处理异常的逻辑可以包括打印错误信息、清理资源、执行恢复操作等。这样可以防止程序崩溃并在必要时继续执行其他代码。
多类型异常捕获 有时候可能需要处理多种类型的异常这时可以使用多个 catch 块就像上面那样也可以使用一个通用的 catch(...) 块来捕获所有异常。
注意 catch(...) 可以捕获任意类型的异常但同时并不能清楚异常的类型所以一般是放在程序的最后用于捕获未知异常的相当于一个保障并且不能放在其他异常捕获代码的前面否则会屏蔽其他异常捕获 2.异常的重新抛出
有时单个的catch不能完全处理一个异常在进行一些校正处理以后希望再交给更外层的调用链函数来处理catch则可以通过重新抛出将异常传递给更上层的函数进行处理
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;
}
三、异常的安全与规范
1.异常安全
由于异常处理会导致程序执行流程的随意跳转过多或任意地使用异常可能会导致代码混乱而且随意抛出异常会有资源泄露的风险所以下面几种情况最好不要抛出异常 1. 构造函数完成对象的构造和初始化最好不要在构造函数中抛出异常否则可能导致对象不完整或没有完全初始化 2. 析构函数主要完成资源的清理最好不要在析构函数内抛出异常否则可能导致资源泄漏(内 存泄漏、句柄未关闭等) 3. 在new和delete中最好不要抛出异常可能会导致内存泄漏 4. 在lock和unlock中最好不要抛出异常可能会导致死锁 2.异常规范
对异常规格说明可以让函数使用者知道函数可能抛出的异常有哪些。可以在函数的后面接throw(类型)列出这个函数可能抛出的所有异常类型。如果函数后面接throw()说明函数不抛出异常若无说明则函数可以抛出任意异常
// 这里表示这个函数会抛出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表示不会抛异常
thread() noexcept;
thread (thread x) noexcept;
3.自定义异常体系
在实际中公司会自定义自己的异常体系进行规范的异常管理通常会定义一套继承的规范体系这样大家抛出的都是继承的派生类对象都只要捕获基类对象就可以了C允许通过基类对象捕获其派生类对象
在 C 中通常继承 std::exception 类标准异常类
#include exception
#include string
using namespace std;// 基类对象
class Custom : public exception {
protected:string message; // 错误信息int errorCode; // 错误码public:Custom(const string msg, int code 0): message(msg), errorCode(code) {}virtual const char* what() const noexcept override {return message.c_str();}int getErrorCode() const { return errorCode; }
};// 派生类对象
class Database : public Custom {
public:Database(const string msg, int code 1001): Custom(Database Error: msg, code) {}
};class Network : public Custom {
public:Network(const string msg, int code 1002): Custom(Network Error: msg, code) {}
};四、异常的优缺点
异常的优缺点如下 优点 缺点1.清晰准确的展示出错误的各种信息1.导致程序的执行流乱跳跟踪调试时比较困难2.直接跳转catch捕获直接处理错误2.额外的性能开销可忽略3.使用场景更为广泛3.内存泄漏的风险可结合智能指针解决4.需要手动定义标准体系
总结异常总体还是利大于弊的在工程中也鼓励使用异常。 以上就是对异常处理的介绍与个人理解欢迎指正~
码文不易还请多多关注支持这是我持续创作的最大动力