1 异常简介
-
C++内置了异常处理的语法元素
try...catch...
try
语句处理正常代码逻辑catch
语句处理异常情况try
语句中的异常由对应的catch
语句处理
-
示例
try { // try语句中的代码块有可能发生异常,该异常由此间代码块中的throw语句抛出(如divide函数中的throw),由try语句接收,再由catch处理 double r = divide(1,0); } catch(...){ cout << "Divided by zero.." << endl; }
-
C++ 通过
throw
语句抛出异常信息double divided(double a,double b){ const double delta = 0.000000000000001; double ret = 0; if(!((-delta < b) && (b < delta))) { ret = a / b; } else { throw 0; // 产生除 0 异常,对应一个值:0,函数此时立即返回 } return ret; }
2 C++ 异常处理分析
-
throw
抛出的异常必须被catch
处理- 当前函数能够处理异常,程序继续往下执行
- 当前函数无法处理异常(没有
try...catch...
),则函数停止执行,并返回
-
未被处理的异常会顺着函数调用栈向上传播,直到被处理为止,否则程序将停止执行
-
示例1:C++ 中的异常处理机制
-
Demo1:未处理异常
#include <iostream> using namespace std; double divide(double a, double b) { const double delta = 0.000000000000001; double ret = 0; if( !((-delta < b) && (b < delta)) ) { ret = a / b; } else { throw 0; // 产生除 0 异常 } return ret; } int main() { cout << "main() begin" << endl; divide(1, 0); cout << "main() end" << endl; return 0; }
-
运行
- 分析:报错原因:由于
divide
函数中抛出了一个异常,divide
函数立即返回(返回一个 0 值)到main
函数中,但是main
函数中没有tyr..catch...
语句块,main
函数向上传递异常,直接报错!
main() begin 报错
- 分析:报错原因:由于
-
Demo2:添加
try...catch...
语句块#include <iostream> using namespace std; double divide(double a, double b) { const double delta = 0.000000000000001; double ret = 0; if( !((-delta < b) && (b < delta)) ) { ret = a / b; } else { throw 0; // 产生除 0 异常 } return ret; } int main() { cout << "main() begin" << endl; try { double c = divide(1, 0); cout << "c = " << c << endl; } catch(...) { cout << "Divided by zero..." << endl; } try { double c = divide(1, 1); cout << "c = " << c << endl; } catch(...) { cout << "Divided by zero..." << endl; } cout << "main() end" << endl; return 0; }
-
运行
main() begin Divided by zero... c = 1 main() end
-
3 异常处理的规则
-
同一个
try
语句可以跟上多个catch
语句catch
语句可以定义具体处理的异常类型- 不同类型的异常由不同的
catch
语句负责处理 try
语句中可以抛出任何类型的异常catch(...)
用于处理所有类型的异常- 任何异常都只能被捕获(
catch
) 一次
-
异常处理的匹配规则
- 异常抛出后,至上而下严格匹配每一个
catch
语句处理的类型 - 异常处理匹配时,不进行任何的类型转换
- 异常抛出后,至上而下严格匹配每一个
-
示例2:多个
catch
语句情况-
Demo
#include <iostream> using namespace std; double divide(double a, double b) { const double delta = 0.000000000000001; double ret = 0; if( !((-delta < b) && (b < delta)) ) { ret = a / b; } else { throw 0; // 产生除 0 异常 } return ret; } // 异常类型是严格匹配的 void test1() { try { // 抛出 char 类型异常 throw 'c'; } // 处理 int 类型异常 catch(int i) { cout << "catch(int i)" << endl; } // 处理 double 类型异常 catch(double d) { cout << "catch(double d)" << endl; } // 处理 char 类型异常 catch(char c) { cout << "catch(char c)" << endl; } } // 任何异常都只能被捕获(catch)一次 void test2() { try { // 抛出字符串(字符串字面常量:const char*)异常 throw "ABCD"; } catch(char* c) { cout << "catch(char* c)" << endl; } catch(const char* cc) { cout << "catch(const char* cc)" << endl; } // 能够处理所有类型异常的 catch 放在最后 catch(...) { cout << "catch(...)" << endl; } } int main() { test1(); test2(); return 0; }
-
运行
catch(char c) catch(const char* cc)
-
4 数据结构中的异常类构建
-
除基本类型外,异常的类型还可以是自定义类类型
-
对于类类型异常的匹配依旧是至上而下严格匹配
-
(面向对象中的)赋值兼容性原则(子类对象赋值给父类对象)在异常匹配中依然能适用
-
一般而言:匹配子类异常的
catch
放在上部 ,匹配父类异常的catch
放在下部 =>ArithmeticException
类的使用 -
现代 C++ 库必然包含充要的异常类族,异常类是数据结构类所依赖的“基础设施”
-
异常类功能定义
异常类 功能定义 ArithmeticException 计算异常 NullPointerException 空指针异常 IndexOutOfBoundsException 越界异常 NoEnoughMemoryException 内存不足异常 InvalidParameterException 参数错误异常 -
异常类中的接口定义
class Exception { public: Exception(const char* message); Exception(const char* file,int line); Exception(const char* message,const char* file,int line); Exception(const Exception& e); Exception& operator= (const Exception& e); virtual const char* message() const; // 异常的详细信息 virtual const char* location() const; // 产生异常的文件及相应的行号 virtual ~Exception() = 0; // 纯虚函数,说明当前类是一个抽象类 }
-
创建异常类族
-
Exception
抽象类//Exception.h #ifndef _EXCEPTION_H_ #define _EXCEPTION_H_ namespace DTLib { class Exception { protected: //成员变量:说明当前异常的详细信息 char* m_message; //成员变量:说明发生异常的地点 char* m_location; //辅助函数:用于重载构造函数 void init(const char* message, const char* file, int line); public: //3个构造函数 Exception(const char* message); Exception(const char* file, int line); Exception(const char* message, const char* file, int line); //拷贝构造函数 Exception(const Exception& e); //重载“=”操作符 Exception& operator= (const Exception& e); virtual const char* message() const; virtual const char* location() const; //这里为了做测试,改为虚函数 virtual ~Exception(); }; } #endif //Exception.cpp #include "Exception.h" #include <cstring> #include <cstdlib> using namespace std; namespace DTLib { void Exception::init(const char* message, const char* file, int line) { //拷贝字符串,此时m_message指向的字符串(与message所指向的字符串相同)在堆中(而message可能在堆中,也有可能在全局数据区中),由m_essage自己控制 m_message = strdup(message); if (file != NULL) { //将发生异常的行号转换为字符串 //定义一个辅助字符数组,保存发生异常的文件名 char sl[16] = { 0 }; itoa(line, sl, 10); m_location = static_cast<char*>(malloc(strlen(file) + strlen(sl) + 2)); //动态内存申请成功 if(m_location != NULL) { m_location = strcpy(m_location, file); m_location = strcat(m_location, ":"); m_location = strcat(m_location, sl); } } else { m_location = NULL; } } Exception::Exception(const char* message) { init(message, NULL, 0); } Exception::Exception(const char* file, int line) { init(NULL, file, line); } Exception::Exception(const char* message, const char* file, int line) { init(message, file, line); } //用于深拷贝 Exception::Exception(const Exception& e) { m_message = strdup(e.m_message); m_location = strdup(e.m_location); } Exception& Exception::operator= (const Exception& e) { if (this != &e) { free(m_message); free(m_location); m_message = strdup(e.m_message); m_location = strdup(e.m_location); } return *this; } const char* Exception::message() const { return m_message; } const char* Exception::location() const { return m_location; } //纯虚函数不需要实现,但析构函数必须提供实现 Exception:: ~Exception() { free(m_message); free(m_location); } }
-
Exception
类的使用#include <iostream> #include "Exception.h" using namespace std; using namespace DTLib; int main() { try{ throw Exception("test",__FILE__,__LINE__); } catch(const Exception& e){ cout << "catch(const Exception& e)" << endl; cout << e.message() << endl; cout<< e.location() << endl; } return 0; }
-
运行输出
catch(const Exception& e) test ***main.cpp:15
-
Exception
抽象类改进:利用宏,在需要的地方,直接扔出异常的类型和信息即可,不需要再管理文件名和行号(宏直接添加了)//Exception.h #ifndef _EXCEPTION_H_ #define _EXCEPTION_H_ namespace DTLib { //定义一个宏:说明异常信息,第一个参数e为异常类型,第二个参数m为异常的详细信息 #define THROW_EXCEPTION(e,m) (throw e(m,__FILE__,__LINE__)) class Exception { protected: char* m_message; char* m_location; void init(const char* message, const char* file, int line); public: Exception(const char* message); Exception(const char* file, int line); Exception(const char* message, const char* file, int line); Exception(const Exception& e); Exception& operator= (const Exception& e); virtual const char* message() const; virtual const char* location() const; virtual ~Exception(); }; } #endif
-
改进的
Exception
类的使用#include <iostream> #include "Exception.h" using namespace std; using namespace DTLib; int main() { try{ //用宏抛出异常 THROW_EXCEPTION(Exception,"test"); } catch(const Exception& e){ cout << "catch(const Exception& e)" << endl; cout << e.message() << endl; cout<< e.location() << endl; } return 0; }
-
ArithmeticException
类//Exception.h namespace DTLib { class ArithmeticException : public Exception { public: //无参构造 ArithmeticException() : Exception(0,0,0) {} //有参构造 ArithmeticException(const char* message) : Exception(message,0,0) {} ArithmeticException(const char* file,int line) : Exception(0,file,0) {} ArithmeticException(const char* message,const char* file,int line) : Exception(message,file,line) {} //拷贝构造 ArithmeticException(const ArithmeticException& e) : Exception(e) {} //"=" ArithmeticException& operator= (const ArithmeticException& e){ Exception::operator=(e); return *this; } }; }
-
AithmeticException
类的使用//main.cpp int main() { try{ THROW_EXCEPTION(ArithmeticException,"test"); } //匹配子类异常的 catch 放在上部 ,匹配父类异常的 catch 放在下部 catch(const ArithmeticException& e){ cout << "catch(const ArithmeticException& e)" << endl; cout << e.message() << endl; cout << e.location() << endl; } //用子类对象赋值给父类对象 catch(const Exception& e){ cout << "catch(const Exception& e)" << endl; cout << e.message() << endl; cout << e.location() << endl; } return 0; }
-
运行输出
catch(const ArithmeticException& e) test ***main.cpp:15
-