婚恋网站开发背景文献,在线咨询网站模板,山东圣大建设集团网站,做网站开发要安装哪些软件异常规范与自定义异常类的设计 大家好#xff5e; 上一篇我们聊了 C 异常处理的基础——try-catch-throw 的基本用法#xff0c;学会了如何捕获和处理简单异常。但在实际开发中#xff0c;仅用基础用法远远不够#xff1a;比如多个函数抛出不同异常时#xff0c;如何清晰…异常规范与自定义异常类的设计大家好 上一篇我们聊了 C 异常处理的基础——try-catch-throw 的基本用法学会了如何捕获和处理简单异常。但在实际开发中仅用基础用法远远不够比如多个函数抛出不同异常时如何清晰约束抛出类型系统自带的异常比如字符串、int 错误码无法携带足够的错误信息该怎么办今天我们就来解决这两个问题聊聊异常规范和自定义异常类的设计让我们的异常处理更规范、更灵活适配复杂项目的开发需求。话不多说咱们直奔主题全程搭配可直接运行的示例代码新手也能轻松跟上一、先搞懂什么是异常规范异常规范简单来说就是对函数可能抛出的异常类型进行明确声明告诉调用者“这个函数只会抛出这些类型的异常其他类型的异常不会从这里抛出”。为什么需要异常规范举个例子如果一个函数没有任何异常声明调用者无法提前预知它会抛出什么异常只能用 catch (…) 万能捕获既不规范也不利于排查错误而有了异常规范调用者可以精准捕获对应类型的异常代码的可读性和可维护性会大大提升。1. C 中的异常规范写法两种方式C 中异常规范有两种常用写法一种是 C98 引入的 throw 列表目前已被弃用但仍有老代码在使用另一种是 C11 引入的 noexcept 关键字推荐使用更简洁、更高效。1C98 异常规范throw 列表弃用了解即可语法格式在函数声明/定义的末尾加上 throw(异常类型1, 异常类型2, …)表示该函数只会抛出括号内列出的异常类型。#includeiostream#includestringusingnamespacestd;// 异常规范该函数只会抛出 int 或 string 类型的异常voidfunc()throw(int,string){intflag1;if(flag1){throw100;// 允许抛出 int 类型}elseif(flag2){throwstring(函数执行失败);// 允许抛出 string 类型}// 不能抛出其他类型比如 char*// throw 错误; // 编译可能报警告运行时可能触发 terminate()}intmain(){try{func();}catch(interrCode){cout捕获到 int 异常errCodeendl;}catch(string errMsg){cout捕获到 string 异常errMsgendl;}return0;}补充说明如果函数末尾写 throw()表示该函数不会抛出任何异常这种写法的缺点编译器检查不严格比如上面抛出 char* 类型有些编译器仅报警告不会报错且运行时开销较大所以 C11 后被弃用不推荐在新项目中使用。2C11 推荐noexcept 关键字重点掌握noexcept 是 C11 引入的用来替代 throw 列表语法更简洁编译器检查更严格运行时开销更小是目前主流的异常规范方式。核心用法分两种noexcept表示该函数绝对不会抛出任何异常noexcept(条件)表示当条件为 true 时函数不会抛出异常条件为 false 时可能抛出异常较少用。#includeiostream#includestringusingnamespacestd;// 异常规范该函数绝对不会抛出任何异常推荐写法voidfunc1()noexcept{coutfunc1不会抛出异常endl;// 如果强行抛出异常编译器会直接调用 terminate()程序崩溃// throw 100; // 运行时崩溃提示 terminate called after throwing an instance of int}// 异常规范该函数可能抛出异常不写 noexcept 等价于 noexcept(false)voidfunc2(){throwstring(func2抛出 string 异常);}intmain(){try{func1();func2();}catch(string errMsg){cout捕获到异常errMsgendl;}return0;}关键注意点如果一个函数声明为 noexcept但内部强行抛出异常程序会直接崩溃调用 terminate()不会进入 catch 块如果函数没有任何异常规范不写 throw 列表也不写 noexcept等价于 noexcept(false)表示“可能抛出任何类型的异常”建议明确不会抛出异常的函数一定要加 noexcept比如工具函数、简单的getter/setter可以提升程序运行效率可能抛出异常的函数无需刻意声明默认 noexcept(false)。二、核心重点自定义异常类的设计上一篇我们用 int、string 作为异常值抛出但在实际开发中这些类型的异常有明显的缺点携带的信息有限比如抛出 int 错误码调用者需要记住每个错误码对应的含义维护成本高无法区分异常来源多个函数都抛出 string 类型异常无法快速判断异常是来自哪个函数、哪个模块扩展性差无法根据需求添加额外的异常信息比如异常发生的行号、时间、模块名。此时自定义异常类就派上用场了。我们可以通过类的封装让异常携带更丰富的信息区分不同来源的异常还能实现异常的继承和扩展适配复杂项目的需求。1. 自定义异常类的设计原则必看设计自定义异常类时遵循以下3个原则能让异常处理更规范、更灵活继承自 C 标准异常类std::exception标准异常类提供了基础的异常接口比如 what() 方法用来返回异常描述继承它可以让我们的自定义异常和系统异常兼容方便统一处理提供 what() 方法重写 std::exception 的 what() 方法返回具体的异常描述信息比如“文件打开失败路径xxx”支持异常信息的自定义通过构造函数传入异常相关信息比如错误码、模块名、描述让异常携带足够的调试信息。2. 基础示例自定义异常类单异常类先从最简单的自定义异常类开始继承 std::exception重写 what() 方法支持传入异常描述和错误码。#includeiostream#includestring#includeexception// 必须包含std::exception 所在头文件usingnamespacestd;// 自定义异常类继承自 std::exceptionclassMyException:publicexception{private:interrCode;// 错误码string errMsg;// 异常描述string errModule;// 异常所在模块可选提升实用性public:// 构造函数初始化异常信息支持自定义错误码、模块、描述MyException(intcode,conststringmodule,conststringmsg):errCode(code),errModule(module),errMsg(msg){}// 重写 what() 方法返回异常描述必须是 const char* 类型virtualconstchar*what()constnoexceptoverride{// 拼接异常信息模块 错误码 描述staticstring fullMsg;// 静态变量避免返回局部变量地址fullMsg[errModule] 错误码to_string(errCode)描述errMsg;returnfullMsg.c_str();// 转换为 const char* 返回}// 可选提供获取错误码、模块的接口方便后续处理intgetErrCode()constnoexcept{returnerrCode;}stringgetErrModule()constnoexcept{returnerrModule;}};// 测试函数抛出自定义异常voidopenFile(conststringfilePath){// 模拟文件打开失败的场景if(filePath.empty()){// 抛出自定义异常错误码1001模块FileOperate描述文件路径为空throwMyException(1001,FileOperate,文件路径为空无法打开文件);}cout文件打开成功filePathendl;}intmain(){try{// 调用可能抛出自定义异常的函数openFile();// 传入空路径触发异常}// 捕获自定义异常匹配 MyException 类型catch(constMyExceptione){cout捕获到自定义异常e.what()endl;cout异常模块e.getErrModule()endl;cout错误码e.getErrCode()endl;// 可以根据错误码做不同的处理if(e.getErrCode()1001){cout建议请传入有效的文件路径endl;}}// 兜底捕获防止其他未预料到的异常catch(...){cout捕获到未知异常endl;}return0;}运行结果捕获到自定义异常[FileOperate] 错误码1001描述文件路径为空无法打开文件 异常模块FileOperate 错误码1001 建议请传入有效的文件路径关键点解析必须包含 头文件否则无法继承 std::exceptionwhat() 方法必须是 const 修饰且 noexceptC11 后推荐返回值是 const char*用静态变量拼接异常信息避免返回局部变量的地址局部变量生命周期结束后地址会失效导致乱码捕获自定义异常时建议用 const 引用const MyException e避免拷贝提升效率。3. 进阶示例自定义异常类的继承多异常场景在复杂项目中不同模块可能会抛出不同类型的异常比如文件操作异常、网络异常、数据库异常此时可以设计一个“基类异常”再让各个模块的异常类继承它实现异常的分层处理。比如基类 Exception → 子类 FileException文件异常、NetworkException网络异常这样调用者可以精准捕获某个模块的异常也可以捕获基类异常实现统一处理。#includeiostream#includestring#includeexceptionusingnamespacestd;// 异常基类继承自 std::exception所有自定义异常的父类classBaseException:publicexception{private:interrCode;string errMsg;string errModule;public:BaseException(intcode,conststringmodule,conststringmsg):errCode(code),errModule(module),errMsg(msg){}virtualconstchar*what()constnoexceptoverride{staticstring fullMsg;fullMsg[errModule] 错误码to_string(errCode)描述errMsg;returnfullMsg.c_str();}intgetErrCode()constnoexcept{returnerrCode;}stringgetErrModule()constnoexcept{returnerrModule;}};// 子类1文件异常继承自基类异常classFileException:publicBaseException{public:// 构造函数调用父类构造函数固定模块为FileModuleFileException(intcode,conststringmsg):BaseException(code,FileModule,msg){}};// 子类2网络异常继承自基类异常classNetworkException:publicBaseException{public:// 构造函数调用父类构造函数固定模块为NetworkModuleNetworkException(intcode,conststringmsg):BaseException(code,NetworkModule,msg){}};// 测试函数1文件操作抛出 FileExceptionvoidreadFile(conststringfilePath){if(filePath.find(.txt)string::npos){// 抛出文件异常错误码2001描述不是txt文件throwFileException(2001,文件格式错误仅支持.txt文件);}cout读取文件成功filePathendl;}// 测试函数2网络连接抛出 NetworkExceptionvoidconnectNetwork(conststringip){if(ip127.0.0.1){// 抛出网络异常错误码3001描述本地IP无法连接throwNetworkException(3001,本地IP127.0.0.1无法建立远程连接);}cout网络连接成功ipendl;}intmain(){try{readFile(test.docx);// 触发文件异常connectNetwork(127.0.0.1);// 触发网络异常不会执行因为上面已经抛出异常}// 捕获文件异常子类异常优先匹配catch(constFileExceptione){cout捕获到文件异常e.what()endl;}// 捕获网络异常子类异常catch(constNetworkExceptione){cout捕获到网络异常e.what()endl;}// 捕获基类异常如果子类异常未被单独捕获会被基类捕获实现统一处理catch(constBaseExceptione){cout捕获到基础异常e.what()endl;}catch(...){cout捕获到未知异常endl;}return0;}运行结果捕获到文件异常[FileModule] 错误码2001描述文件格式错误仅支持.txt文件进阶要点异常子类继承基类后无需重写 what() 方法除非需要自定义子类的异常格式直接复用父类的实现即可catch 块的顺序很重要子类异常必须放在基类异常前面如果先捕获 BaseException子类异常会被基类捕获子类的 catch 块永远不会执行这种分层设计的优势可以单独处理某个模块的异常比如文件异常做重试操作也可以通过捕获基类异常统一处理所有自定义异常比如统一记录异常日志。三、常见避坑点重点提醒自定义异常类和异常规范很容易踩坑这部分一定要记好避免写出无效的异常处理代码自定义异常类必须继承 std::exception且重写 what() 方法否则无法和系统异常兼容也无法正常捕获what() 方法的返回值必须是 const char*且不能返回局部变量的地址建议用静态变量或堆内存堆内存需注意释放避免内存泄漏noexcept 不要滥用可能抛出异常的函数不要声明为 noexcept否则强行抛出异常会导致程序崩溃catch 块顺序子类异常在前基类异常在后具体类型异常在前万能捕获在后捕获异常时优先用 const 引用const 异常类 e避免拷贝异常对象提升效率同时避免切片问题子类异常被基类对象捕获时丢失子类特有信息。四、总结与实践建议今天我们掌握了两个核心知识点异常规范和自定义异常类的设计总结一下异常规范推荐用 C11 的 noexcept明确不会抛出异常的函数加 noexcept提升效率自定义异常类继承 std::exception重写 what() 方法携带足够的异常信息错误码、模块、描述复杂项目建议用“基类子类”的分层设计核心目标让异常处理更规范、更易调试、更易维护避免滥用万能捕获让错误处理更精准。实践建议把今天的示例代码复制到编译器中运行亲手修改异常信息、错误码感受异常的捕获和处理流程尝试自己设计一个自定义异常子类比如 DatabaseException 数据库异常添加特有属性比如数据库连接地址在实际项目中给每个模块设计对应的异常类统一异常规范方便后续排查错误和维护。