C++中异常处理的基本思想及throw语句抛出异常的使用

 更新时间:2016年03月14日 15:38:20   作者:YoferZhang  
这篇文章主要介绍了C++中异常处理的基本思想及throw类抛出异常的使用,也深入谈到了异常被抛出后的栈解旋unwinding过程,需要的朋友可以参考下

异常处理基本思想
C++的异常处理的基本思想大致可以概括为传统错误处理机制、通过函数返回值来处理错误。

2016314153351173.jpg (377×335)

1)C++的异常处理机制使得异常的引发和异常的处理不必在同一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理。上层调用者可以再适当的位置设计对不同类型异常的处理。
2)异常是专门针对抽象编程中的一系列错误处理的,C++中不能借助函数机制,因为栈结构的本质是先进后出,依次访问,无法进行跳跃,但错误处理的特征却是遇到错误信息就想要转到若干级之上进行重新尝试,如图

3)异常超脱于函数机制,决定了其对函数的跨越式回跳。
4)异常跨越函数

异常基本语法

2016314153429533.jpg (577×329)

1) 若有异常则通过throw操作创建一个异常对象并抛掷。
2) 将可能抛出异常的程序段嵌在try块之中。控制通过正常的顺序执行到达try语句,然后执行try块内的保护段。
3) 如果在保护段执行期间没有引起异常,那么跟在try块后的catch子句就不执行。程序从try块后跟随的最后一个catch子句后面的语句继续执行下去。
4) catch子句按其在try块后出现的顺序被检查。匹配的catch子句将捕获并处理异常(或继续抛掷异常)。
5) 如果匹配的处理器未找到,则运行函数terminate将被自动调用,其缺省功能是调用abort终止程序。
6)处理不了的异常,可以在catch的最后一个分支,使用throw语法,向上扔
7)异常机制与函数机制互不干涉,但捕捉的方式是基于类型匹配。捕捉相当于函数返回类型的匹配,而不是函数参数的匹配,所以捕捉不用考虑一个抛掷中的多种数据类型匹配问题。
catch代码块必须出现在try后,并且在try块后可以出现多个catch代码块,以捕捉各种不同类型的抛掷。
异常机制是基于这样的原理:程序运行实质上是数据实体在做一些操作,因此发生异常现象的地方,一定是某个实体出了差错,该实体所对应的数据类型便作为抛掷和捕捉的依据。
8)异常捕捉严格按照类型匹配
 异常捕捉的类型匹配之苛刻程度可以和模板的类型匹配媲美,它不允许相容类型的隐式转换,比如,抛掷char类型用int型就捕捉不到.例如下列代码不会输出“int exception.”,从而也不会输出“That's ok.” 因为出现异常后提示退出

int main(){ 
  try{ 
    throw ‘H'; 
  } 
  catch (int){ 
    cout << "int exception.\n"; 
  } 
  cout << "That's ok.\n"; 
 
  return 0; 
} 

栈解旋(unwinding)
异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反。这一过程称为栈的解旋(unwinding)。

#include <iostream> 
#include <cstdio> 
using namespace std; 
 
 
class MyException {}; 
 
class Test 
{ 
public: 
  Test(int a = 0, int b = 0) 
  { 
    this->a = a; 
    this->b = b; 
    cout << "Test 构造函数执行" << "a:" << a << " b: " << b << endl; 
  } 
  void printT() 
  { 
    cout << "a:" << a << " b: " << b << endl; 
  } 
  ~Test() 
  { 
    cout << "Test 析构函数执行" << "a:" << a << " b: " << b << endl; 
  } 
private: 
  int a; 
  int b; 
}; 
 
void myFunc() throw (MyException) 
{ 
  Test t1; 
  Test t2; 
 
  cout << "定义了两个栈变量,异常抛出后测试栈变量的如何被析构" << endl; 
 
  throw MyException(); 
} 
 
int main() 
{ 
  //异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象, 
  //都会被自动析构。析构的顺序与构造的顺序相反。 
  //这一过程称为栈的解旋(unwinding) 
  try 
  { 
    myFunc(); 
  } 
  //catch(MyException &e) //这里不能访问异常对象 
  catch (MyException) //这里不能访问异常对象 
  { 
    cout << "接收到MyException类型异常" << endl; 
  } 
  catch (...) 
  { 
    cout << "未知类型异常" << endl; 
  } 
 
  return 0; 
} 

异常接口声明
1)为了加强程序的可读性,可以在函数声明中列出可能抛出的所有异常类型,例如:
void func() throw (A, B, C , D); //这个函数func()能够且只能抛出类型A B C D及其子类型的异常。
2)如果在函数声明中没有包含异常接口声明,则次函数可以抛掷任何类型的异常,例如:
void func();
3)一个不抛掷任何类型异常的函数可以声明为:
void func() throw();
4) 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexpected函数会被调用,该函数默认行为调用terminate函数中止程序。

传统处理错误

#include <iostream> 
#include <cstdio> 
using namespace std; 
 
// 传统的错误处理机制 
int myStrcpy(char *to, char *from) 
{ 
  if (from == NULL) { 
    return 1; 
  } 
  if (to == NULL) { 
    return 2; 
  } 
 
  // copy时的场景检查 
  if (*from == 'a') { 
    return 3; // copy时错误 
  } 
  while (*from != '\0') { 
    *to = *from; 
    to++; 
    from++; 
  } 
  *to = '\0'; 
 
  return 0; 
} 
 
int main() 
{ 
  int ret = 0; 
  char buf1[] = "zbcdefg"; 
  char buf2[1024] = { 0 }; 
 
  ret = myStrcpy(buf2, buf1); 
  if (ret != 0) { 
    switch (ret) { 
    case 1: 
      cout << "源buf出错!\n"; 
      break; 
    case 2: 
      cout << "目的buf出错!\n"; 
      break; 
    case 3: 
      cout << "copy过程出错!\n"; 
      break; 
    default: 
      cout << "未知错误!\n"; 
      break; 
    } 
  } 
  cout << "buf2:\n" << buf2; 
  cout << endl; 
 
  return 0; 
} 

throw char*

#include <iostream> 
#include <cstdio> 
using namespace std; 
 
// throw char * 
void myStrcpy(char *to, char *from) 
{ 
  if (from == NULL) { 
    throw "源buf出错"; 
  } 
  if (to == NULL) { 
    throw "目的buf出错"; 
  } 
 
  // copy时的场景检查 
  if (*from == 'a') { 
    throw "copy过程出错"; // copy时错误 
  } 
  while (*from != '\0') { 
    *to = *from; 
    to++; 
    from++; 
  } 
  *to = '\0'; 
 
  return; 
} 
 
int main() 
{ 
  int ret = 0; 
  char buf1[] = "abcdefg"; 
  char buf2[1024] = { 0 }; 
 
  try 
  { 
    myStrcpy(buf2, buf1); 
  } 
  catch (int e) // e可以写可以不写 
  { 
    cout << e << "int类型异常" << endl; 
  } 
  catch (char *e) 
  { 
    cout << "char* 类型异常" << endl; 
  } 
  catch (...) 
  { 
  }; 
  cout << endl; 
 
  return 0; 
} 

throw 类对象

#include <iostream> 
#include <cstdio> 
using namespace std; 
 
class BadSrcType {}; 
class BadDestType {}; 
class BadProcessType 
{ 
public: 
  BadProcessType() 
  { 
    cout << "BadProcessType构造函数do \n"; 
  } 
 
 
  BadProcessType(const BadProcessType &obj) 
  { 
    cout << "BadProcessType copy构造函数do \n"; 
  } 
 
  ~BadProcessType() 
  { 
    cout << "BadProcessType析构函数do \n"; 
  } 
 
}; 

 
throw 类对象、类型异常 

void my_strcpy3(char *to, char *from) 
{ 
  if (from == NULL) 
  { 
    throw BadSrcType(); 
  } 
  if (to == NULL) 
  { 
    throw BadDestType(); 
  } 
 
  //copy是的 场景检查 
  if (*from == 'a') 
  { 
    printf("开始 BadProcessType类型异常 \n"); 
    throw BadProcessType(); //会不会产生一个匿名对象? 
  } 
 
  if (*from == 'b') 
  { 
    throw &(BadProcessType()); //会不会产生一个匿名对象? 
  } 
 
  if (*from == 'c') 
  { 
    throw new BadProcessType; //会不会产生一个匿名对象? 
  } 
  while (*from != '\0') 
  { 
    *to = *from; 
    to++; 
    from++; 
  } 
  *to = '\0'; 
} 
 
int main() 
{ 
  int ret = 0; 
  char buf1[] = "cbbcdefg"; 
  char buf2[1024] = { 0 }; 
 
  try 
  { 
    //my_strcpy1(buf2, buf1); 
    //my_strcpy2(buf2, buf1); 
    my_strcpy3(buf2, buf1); 
  } 
  catch (int e) //e可以写 也可以不写 
  { 
    cout << e << " int类型异常" << endl; 
  } 
  catch (char *e) 
  { 
    cout << e << " char* 类型异常" << endl; 
  } 
 
  //--- 
  catch (BadSrcType e) 
  { 
    cout << " BadSrcType 类型异常" << endl; 
  } 
  catch (BadDestType e) 
  { 
    cout << " BadDestType 类型异常" << endl; 
  } 
  //结论1: 如果 接受异常的时候 使用一个异常变量,则copy构造异常变量.  
  /* 
  catch( BadProcessType e) //是把匿名对象copy给e 还是e还是那个匿名对象 
  { 
  cout << " BadProcessType 类型异常" << endl; 
  } 
  */ 
  /*结论2: 使用引用的话 会使用throw时候的那个对象 
  catch( BadProcessType &e) //是把匿名对象copy给e 还是e还是那个匿名对象 
  { 
  cout << " BadProcessType 类型异常" << endl; 
  } 
  */ 
 
  //结论3: 指针可以和引用/元素写在一块 但是引用和元素不能写在一块 
  catch (BadProcessType *e) //是把匿名对象copy给e 还是e还是那个匿名对象 
  { 
    cout << " BadProcessType 类型异常" << endl; 
    delete e; 
  } 
 
  //结论4: 类对象时, 使用引用比较合适  
 
  // -- 
  catch (...) 
  { 
    cout << "未知 类型异常" << endl; 
  } 
 
  return 0; 
} 


相关文章

  • C++修炼之构造函数与析构函数

    C++修炼之构造函数与析构函数

    本章节我们将学习类的6个默认成员函数中的构造函数与析构函数,并对比C语言阶段的内容来学习它们的各自的特性,感兴趣的同学可以参考阅读
    2023-03-03
  • C++ 中const 类型限定符不兼容问题

    C++ 中const 类型限定符不兼容问题

    这篇文章主要介绍了C++ 中const 类型限定符不兼容问题的相关资料,需要的朋友可以参考下
    2015-06-06
  • 深入Linux grep指令的详解(实用型)

    深入Linux grep指令的详解(实用型)

    本篇文章是对Linux下的grep指令进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • C++超详细讲解树与二叉树

    C++超详细讲解树与二叉树

    在之前的文章里,我们学习的一直是一对一的线性结构,可现实中,还有很多一对多的情况需要处理,所以我们需要研究这样一种一对多的数据结构——树
    2022-05-05
  • C语言详细讲解多维数组与多维指针

    C语言详细讲解多维数组与多维指针

    C 语言中的多维数组(multidimensional array)其实就是元素为数组的数组。多维指针根据声明的维数需要进行多次地址转换才能够取到目标数据。但指针作为数据变量,可以多次赋值,使其成为对数组操作访问的一大利器,所以指针和数组的结合才是重中之重
    2022-04-04
  • 详解C语言结构体,枚举,联合体的使用

    详解C语言结构体,枚举,联合体的使用

    这篇文章主要给大家介绍一下关于C语言中结构体、枚举、联合体的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考一下
    2022-07-07
  • C语言中对字母进行大小写转换的简单方法

    C语言中对字母进行大小写转换的简单方法

    这篇文章主要介绍了C语言中对字母进行大小写转换的简单方法,是C语言入门学习中的基础知识,需要的朋友可以参考下
    2015-08-08
  • Qt 实现画线笔锋效果详细原理及示例代码

    Qt 实现画线笔锋效果详细原理及示例代码

    这篇文章主要介绍了Qt 实现画线笔锋效果详细原理及示例代码。文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-04-04
  • C++ 约瑟夫环的实例代码

    C++ 约瑟夫环的实例代码

    这篇文章主要介绍了C++ 约瑟夫环的实例代码的相关资料,希望通过本文能帮助到大家,实现这样的功能,需要的朋友可以参考下
    2017-10-10
  • C语言基础应用处理学生打分 计算时间 最少硬币问题详细过程

    C语言基础应用处理学生打分 计算时间 最少硬币问题详细过程

    很多的问题其实可以用编程来解决作答,本篇文章带你用C语言解决最少硬币问题、计算已经过去了多久、学生成绩自动打分来做基础的训练
    2022-02-02

最新评论