首页 > 其他分享 >C-中的异常处理机制

C-中的异常处理机制

时间:2023-07-01 19:55:30浏览次数:42  
标签:f1 处理 try int catch 机制 main 异常

异常处理

传统的C语言处理方法

传返回值表示函数调用是否结束

int f1()
{
    return 0;
    //...
    return 1;
}

这种方法比较简洁明了,但对异常处理的位置(例如我想在main()里处理异常)进行调整局就实现起来十分麻烦了。

更明显的缺点是,这种方法会对函数原本的返回格式产生影响。

C++的异常处理方法:try/catch/throw机制

示例:

#include<iostream>

void f1()
{
   throw 1;
}


int main(int argc, char* argv[])
{
    try {
    f1();
    }
    catch (int)
    {
    std::cout << "exception is occurred.\n";
    }
}

输出:

exception is occurred.

异常触发时的系统行为:栈展开

系统首先会为main()建立一个栈帧,然后为f1()建立一个栈帧。接下来在f1()抛出异常后,在main()里处理异常,同时抛弃f1()的栈帧。这种抛弃的过程称为栈展开。

自然,抛出异常后,会立即处理栈展开,f1()后续的代码不会被处理。局部对象的销毁则按照构造相反的顺序。

若尝试匹配相应的catch代码段,如果匹配则执行其中的逻辑,之后执行catch后续的代码。例如:

#include<iostream>

void f1()
{
  throw 1;
}

void f2()
{   
 try {
  f1();
 }
 catch (int)
 {
  std::cout << "f2 exception is occurred.\n";
 }
 std::cout << "other f2 logic is called.\n";


}

void f3()
{
 f2();
}
int main(int argc, char* argv[])
{
 try {
  f3();
 }
 catch (int)
 {
 std::cout << "exception is occurred.\n";
 }
}

输出:

f2 exception is occurred.
other f2 logic is called.

显然,由于f2()已经捕获到了异常,所以main()内部的catch不再执行,且f2()后续的逻辑可继续执行。

如果不匹配,则继续进行栈展开,直到跳出main()函数,触发terminate结束运行。

异常对象

系统会使用抛出的异常,拷贝初始化一个临时对象,称为异常对象。

try {
  f3();
 }
 catch (int e)
 {
 std::cout << "exception is occurred: " << e <<"\n";
 }

输出:

exception is occurred: 1

在上面的代码中,系统会构造一个值为1的对象,并且不会在栈展开过程中被销毁,并最终传递给对应的catch子句。

try/catch的具体使用方法

1个try子句可以跟一到多个catch子句块。每个catch子句匹配一种类型的异常对象,从上到下依次进行匹配。

假如我们传入的异常对象是一个派生类,例如:

struct Base {};
struct Derive:Base {};

void f1()
{
 throw Derive{};
}

try {
  f1();
 }
 catch (int e)
 {
  std::cout << "exception is occurred: " << e <<"\n";
 }
 catch (Base& e)
 {
  std::cout << "Base exception is occurred.\n";
 }
 catch (Derive& e)
 {
  std::cout << "Deriver exception is occurred.\n";
 }

此时会输出:

Base exception is occurred.

此时先构造一个Derive对象,从上至下先匹配到Base&的catch子句,导致后面的匹配永远不会执行。如果我们想匹配Derive的子句,需要人为调换二者的顺序。

还有一种特殊的catch子句catch(...):

catch(...)
{
    std::cout << "exception is occurred.\n"; 
}

这种catch子句可以匹配任意的异常对象,通常放在各个catch子句的最后(如果放在前面,部分编译器甚至会直接报错)。

某些情况下,我们可以在catch子句内部继续throw相同的异常。

void f3()
{
 try {
  f2();
 }
 catch (int e)
 {
  std::cout << "f3 exception is occurred.\n";
  throw;
 }
 std::cout << "other f3 logic is called.\n";
}
int main(int argc, char* argv[])
{
 try {
  f3();
 }
 catch (int e)
 {
 std::cout << "main exception is occurred: " << e <<"\n";
  
 }
 catch (Derive& e)
 {
  std::cout << "Deriver exception is occurred.\n";
 }

输出为:

f3 exception is occurred.
main exception is occurred:1

两次输出表明在f3()捕获到异常后继续throw直到被main()继续捕获到异常。如果catch(...)在前但继续throw是可以编译通过的。

注意:在一个异常没有完成捕获并处理时抛出一个新的异常会导致程序崩溃!因此,不可以在析构函数或者operator delete函数中抛出异常。

通常来说,catch接收的异常类型为引用类型。这样做的目的是防止在拷贝初始化过程中出现抛出异常的操作。

异常与构造、析构函数的关系

假设我们想在类内捕获异常,自然会想到如下方式:

struct Str
{
  Str()
 {
  throw 100;
 }
};

class Cla
{
private:
 Str m_mem;
public:
   Cla()
 {
    try{}
  catch (int)
  {
   std::cout << "exception is catched in Cla.\n";
  }
 }

然而,这种办法无法在类内捕获异常。因为构造函数体内类已经经过了初始化(编译器会隐式构造一个m_mem()的初始化列表)。这个时候需要用function-try-block保护初始化逻辑。

class Cla
{
private:
 Str m_mem;
public:
 Cla()
  try :m_mem{}
 {

 }
 catch (int)
 {
  std::cout << "exception is catched at Cla\n";
  }
};

这样,就可以在类内捕获异常了。这里的try:m_mem{}里的m_mem{}可省略。

同时,假设我们也在main()内部也使用一次try-catch:

int main(int argc, char* argv[])
{
  try {
  Cla obj;
 }
 catch (int)
 {
  std::cout << "exception is catched at main.\n";
 }
  
}

最后输出为:

exception is catched at Cla. 
exception is catched at main.

这里main()也捕获到了异常。这是因为C++标准规定如果某个类的构造函数里出现了function-try-block,编译器会隐式在结尾加上一个throw让程序可以在后续继续接收到异常。

注意:如果在构造函数中抛出异常,已经构造的成员会被销毁,但类本身的析构函数不会被调用

标签:f1,处理,try,int,catch,机制,main,异常
From: https://www.cnblogs.com/wyfc4/p/17519816.html

相关文章

  • VBA下标越界(运行时错误-9)提示问题处理
    问题反馈: 测试:采购在途表行数900行没问题,2300行就会报错。排查处理:测试复现问题点击调试初步判断:caigouzaituarr或shuchuliaojianxuqiu数组越界shuchuliaojianxuqiu如果h大于6万会越界,现在看订单就三百行,每个bom按20个原材料算也不会越界。Jhs是即时库存表的行数,此处应该时chs;......
  • Redis 的 AOF 重写机制
    Redis的AOF重写机制AOF持久化机制简介AOF(AppendOnlyFile)是一种持久化机制,它将Redis的写操作以日志的形式记录在文件中,以保证数据的安全性和可恢复性。AOF持久化机制的优点有以下几个:可以保证数据的完整性,即使发生系统崩溃或者断电,也可以通过AOF文件恢复数据可......
  • SpringBoot 如何优雅的进行全局异常处理?
    在SpringBoot的开发中,为了提高程序运行的鲁棒性,我们经常需要对各种程序异常进行处理,但是如果在每个出异常的地方进行单独处理的话,这会引入大量业务不相关的异常处理代码,增加了程序的耦合,同时未来想改变异常的处理逻辑,也变得比较困难。这篇文章带大家了解一下如何优雅的进行全局异......
  • LRU缓存机制
    LRU缓存题目链接LRU,即Least-Recently-Used。是一种高速缓存替换策略,是一种缓存机制。主要是利用局部性原理。局部性原理分两种,空间局部性和时间局部性。在一个具有良好时间局部性的程序中,被引用过一次的内存位置很可能在不远的将来再被多次应用。在一个具有良好空间局部性......
  • Java中的异常处理
    前言在学习过程中,遇到较少的异常处理,对这方面的知识不太熟。在这次重新学习的时候整理一次。1.异常1.1分类运行时异常:可以被避免,编译时可以忽略。检查时异常:人的问题无法遇见,例如打开不存在的文件错误:编译检查不到,脱离人的控制。1.2处理框架将异常定义为类处理,Th......
  • Node.js 模块化机制原理探究
    前言Node应用是由模块组成的,Node遵循了CommonJS的模块规范,来隔离每个模块的作用域,使每个模块在它自身的命名空间中执行。CommonJS规范的主要内容:模块必须通过module.exports导出对外的变量或接口,通过require()来导入其他模块的输出到当前模块作用域中。CommonJS模块的特......
  • Swift 多Target预编译Preprocessor Macros中添加字段后不生效处理
    在其中一个Target的PreprocessorMacros中添加字段RVCTAG后,代码判断发现不生效代码逻辑如下#ifPROJECTIDreturntrue#elsereturnfalse#endif还需要在BuildSetting---> Swiftcompiler-CustomFlags--->OtherSwfitFlags中添加对应的宏设置,如下 ......
  • Linux Shell文本处理
    预计更新1:基础知识简介和安装基本命令变量和环境变量2:流程控制条件语句循环语句函数3:文件处理文件读写文件权限和所有权文件搜索和替换4:网络和进程网络通信进程管理信号处理5:文本处理正则表达式文本分析和处理生成报告和日志6:用户界面命令行参数和选......
  • Linux Shell文件处理
    预计更新1:基础知识简介和安装基本命令变量和环境变量2:流程控制条件语句循环语句函数3:文件处理文件读写文件权限和所有权文件搜索和替换4:网络和进程网络通信进程管理信号处理5:文本处理正则表达式文本分析和处理生成报告和日志6:用户界面命令行参数和选......
  • 字符串在货币、日期、精度的处理
    1.区域设置--locale模块的setlocale函数区域设置是一个标识特定地理、文化和语言的系统参数。它影响如日期和时间格式、货币和数字格式以及其他地域相关的操作。在Python中,使用 locale.setlocale() 函数可以设置区域设置来适应不同的地区和语言要求。该函数的语法为:local......