
C语言常用的错误处理机制返回错误码用不同数字代表不同的错误需要程序员自己通过数字去查找对应的错误然后决定如何处理。比如很多库的接口函数发生错误的时候会把错误码放到全局变量errno中而我们自己定义的函数大多用return返回错误码获取函数返回值就得到了错误码。举个例子int func() { //.... return -2; } //1.使用库提供的全局变量来获取错误码 FILE* ptr fopen(sdsd.txt,r);//打开一个不存在的文件 cout errno endl;//看看错误码被设置成多少 perror(Error:);//打印错误码对应的错误信息 /* 处理错误的代码 */ //2.使用函数返回值来获取错误码 int errorcode func(); cout Errorcode: errorcode endl; /* 处理错误的代码 */assert终止程序只要发生错误就暴力终止程序。举个例子FILE* ptr fopen(sdsd.txt, r);//打开一个不存在的文件此时ptr收到NULL assert(ptr ! NULL);setjmp和longjmpsetjmp(jmp_buf env)负责存档。它首次被调用时会保存当前程序执行的现场包括CPU的寄存器、栈指针、程序计数器等一切关键信息并将这些信息存入 env 变量中然后返回 0。longjmp(jmp_buf env, int val)负责读档。当它被调用时程序会立即跳转回之前调用 setjmp 的那个点仿佛时光倒流。此时setjmp 会再次返回但返回的值不再是 0而是 longjmp 传入的 val 参数c中的异常c通过抛出和捕获来处理异常。具体到语法上就是throw关键字以及try-catch语句。try-catch语句try-catch语句是一个整体因此try和catch无法单独使用。每一个try块下面都得有1个或1个以上的catch与其进行配对。所以如果你试图在一个try-catch语句中的try块与catch块之间书写代码或者在两个catch块之间书写代码那会导致这个try-catch语句被截断这是语法不允许的。可能发生错误抛出异常的代码被包裹在try块中对异常进行处理的代码被包裹在catch块中每个catch块都对应一个形参抛出的异常通过参数匹配来决定被哪个catch捕获并把异常对象当作实参传递给catch块。你可以把多个catch块看做多个重载的函数。需要注意的是catch...{}可以捕获任意类型。try块和catch块是两个独立的作用域因此try中定义的变量在try外不能使用。throw关键字throw关键字用于抛出包含异常信息的对象抛异常: throw object。object的类型决定了它被哪个catch捕获。异常捕获机制如上throw语句抛出异常之后在函数作用域1,2,3...main逐层向外查找最近的包含该throw语句的try块如果连main函数作用域中也没有找到try块则直接报错因为这说明没有捕获异常的逻辑。相反如果在此过程中找到了最近的 try语句块则再从该try块对应的一个或者多个catch块中找参数与被抛出对象类型相同的catch块这里的类型匹配是强匹配也就是说int和size_t等貌似可以匹配的也算做不匹配。如果参数类型匹配到了就执行catch块里面的异常处理逻辑而后直接从该catch块后面继续执行非catch块的代码。但是如果没有找到匹配的catch块则从当前函数作用域开始继续向外逐层查找try块重复上述所有步骤。记住先找到try块然后才能找匹配的catch块。在throw抛出异常后从当前函数作用域也就是函数栈帧因为函数调用就在栈上开始逐层查找try块在查找的过程中会销毁已经全部查找过的函数的函数栈帧这个过程叫做栈展开因此如果被销毁函数栈帧的函数中还有什么代码没执行也不会执行了因为函数调用栈已经被销毁了正是因此throw抛出的是异常对象的副本而不能抛出异常对象本身。编写一段代码以此验证结论void func1() { try { throw 1; cout func1:try:throw--back endl; } catch (int x) { cout func1: catch(int x) endl; } cout func1:catch--back endl; } void func2() { try { throw x; coutfunc2:try:throw--back endl; } catch (int x) { cout func1: catch(int x) endl; } cout func2:catch--back endl; } int main() { try { func1(); func2(); cout main:try:func2()--back endl; } catch(char c) { cout main: catch(char c) endl; } cout main:catch--back endl; }补充为了防止上面说的直到main函数还没有匹配成功导致报错一般在main函数中的try后面加上catch...{cout未知错误;}。自定义异常体系设计一个异常基类基类中可以定义异常共有的出错信息此后每有一个新的异常就设计一个继承自基类的异常子类在该子类中可以添加这个异常独有的出错信息。异常基类中定义一个处理异常的虚函数之后每个子类都重写这个虚函数使之能够处理该子类代表的异常。catch块的参数只需要一种那就是基类的引用他可以接受所有子类异常并且通过基类的引用调用异常处理函数可以触发多态。即一种catch块处理所有已知异常。举个例子class Exception { public: Exception(const string errmsg, int id) :_errmsg(errmsg) , _id(id) {} virtual string what() const { cout 处理Exception异常 endl; return _errmsg; } protected: string _errmsg; int _id; }; class SqlException : public Exception { public: SqlException(const string errmsg, int id, const string sql) :Exception(errmsg, id) , _sql(sql) {} virtual string what() const { cout 处理SqlException异常 endl; string str SqlException:; str _errmsg; str -; str _sql; return str; } private: const string _sql; }; class CacheException : public Exception { public: CacheException(const string errmsg, int id) :Exception(errmsg, id) {} virtual string what() const { cout 处理 CacheException异常 endl; string str CacheException:; str _errmsg; return str; } }; int main() { try { throw SqlException(error,2,mysql); } catch(Exception x) //可以接受任意子类异常 { x.what(); } }异常规范表示func函数会抛出A/B/C/D...中的某种类型的异常 void func() throw(ABCD...){...};表示func函数不会抛出异常 void func() throw(){...};表示func函数不会抛异常void func() noexcept{...};需要注意的是noexcept规范是强制检查违反了会使程序终止而throw规范只会报警告。函数声明与定义两者的异常规范应该一致不是说声明写了定义就不用写异常安全构造函数完成对象的构造和初始化最好不要在构造函数中抛出异常否则可能导致对象不 完整或没有完全初始化析构函数主要完成资源的清理最好不要在析构函数内抛出异常否则可能导致资源泄漏(内 存泄漏、句柄未关闭等)C中异常经常会导致资源泄漏的问题比如在new和delete中抛出了异常导致内存泄 漏在lock和unlock之间抛出了异常导致死锁C经常使用RAII来解决以上问题。异常重新抛出异常被捕获之后可以在catch里重新抛出向更外层传递规则都是一样的。这里可以用一个例子来说明重新抛出有什么用这里如果func2抛出异常会被main中的catch捕获。然而 func2后面的代码没有执行所以arr1和arr2的空间没有被释放造成内存泄漏。这时候就可以用到异常的重新抛出先在中途捕获这个异常然后释放动态资源然后重新抛出让main函数中的catch捕获。只写一个throw表示捕获到什么就重新抛出什么难道这样就完美了吗事实上new失败的话也会抛异常如果创建arr2时抛出异常那么arr1就不会被释放所以在arr1处还要加一个重新捕获。这样太麻烦后续可以用智能指针处理。读者可能会有疑问直接在main函数的catch块中把之前所有开辟过的动态空间释放掉不就可以了嘛这样就不用重新捕获再抛出捕获好几次了。实际上main函数捕获到异常的时候并不知道是在哪儿抛出的异常也就不知道开辟了哪些动态空间况且就算知道了也没有动态空间的指针。再者main函数中catch的主要功能就是处理异常至于内存泄漏的问题要是也牵扯给它那就有点混乱了。