C++ throw(抛出异常)详解

抛出(Throw)--> 检测(Try) --> 捕获(Catch)

异常必须显式地抛出,才能被检测和捕获到;如果没有显式的抛出,即使有异常也检测不到。

在 C++ 中,我们使用 throw 关键字来显式地抛出异常,它的用法为:

throw exceptionData;

exceptionData 是“异常数据”的意思,它可以包含任意的信息,完全有程序员决定。exceptionData 可以是 int、float、bool 等基本类型,也可以是指针、数组、字符串、结构体、类等聚合类型,请看下面的例子:

char str[] = "hello world 666";
char *pstr = str;
class Base{};
Base obj;
throw 100;//int 类型
throw str;//数组类型
throw pstr;//指针类型
throw obj;//对象类型

动态数组的例子

C/C++ 规定,数组一旦定义后,它的长度就不能改变了;换句话说,数组容量不能动态地增大或者减小。这样的数组称为静态数组(Static array)。静态数组有时候会给编码代码不便,我们可以通过自定义的 Array 类来实现动态数组(Dynamic array)。所谓动态数组,是指数组容量能够在使用的过程中随时增大或减小。

#include <iostream>
#include <cstdlib>using namespace std;
//自定义的异常类型
class OutOfRange
{public:OutOfRange() : m_flag(1) { };OutOfRange(int len, int index) : m_len(len), m_index(index), m_flag(2) { }
public:void what() const;  //获取具体的错误信息
private:int m_flag;  //不同的flag表示不同的错误int m_len;  //当前数组的长度int m_index;  //当前使用的数组下标
};
void OutOfRange::what() const
{if (m_flag == 1) {cout << "Error: empty array, no elements to pop." << endl;}else if (m_flag == 2) {cout << "Error: out of range( array length " << m_len << ", access index " << m_index << " )" << endl;}else {cout << "Unknown exception." << endl;}
}
//实现动态数组
class Array
{public:Array();~Array() { free(m_p); };
public:int operator[](int i) const;//获取数组元素int push(int ele);//在末尾插入数组元素int pop();//在末尾删除数组元素int length() const { return m_len; };//获取数组长度
private:int m_len;//数组长度int m_capacity;//当前的内存能容纳多少个元素int* m_p;//内存指针
private:static const int m_stepSize = 50;//每次扩容的步长
};Array::Array()
{m_p = (int*)malloc(sizeof(int) * m_stepSize);m_capacity = m_stepSize;m_len = 0;
}
int Array::operator[](int index) const
{if (index < 0 || index >= m_len) {//判断是否越界throw OutOfRange(m_len, index);//抛出异常(创建一个匿名对象)}return *(m_p + index);
}
int Array::push(int ele)
{if (m_len >= m_capacity) { //如果容量不足就扩容m_capacity += m_stepSize;m_p = (int*)realloc(m_p, sizeof(int) * m_capacity);  //扩容}*(m_p + m_len) = ele;m_len++;return m_len - 1;
}
int Array::pop()
{if (m_len == 0) {throw OutOfRange();  //抛出异常(创建一个匿名对象)}m_len--;return *(m_p + m_len);
}
//打印数组元素
void printArray(Array& arr)
{int len = arr.length();//判断数组是否为空if (len == 0) {cout << "Empty array! No elements to print." << endl;return;}for (int i = 0; i < len; i++) {if (i == len - 1) {cout << arr[i] << endl;}else {cout << arr[i] << ", ";}}
}
int main()
{Array nums;//向数组中添加十个元素for (int i = 0; i < 10; i++) {nums.push(i);}printArray(nums);//尝试访问第20个元素try {cout << nums[20] << endl;}catch (OutOfRange& e) {e.what();}//尝试弹出20个元素try {for (int i = 0; i < 20; i++) {nums.pop();}}catch (OutOfRange& e) {e.what();}printArray(nums);return 0;
}


Array 类实现了动态数组,它的主要思路是:在创建对象时预先分配出一定长度的内存(通过 malloc() 分配),内存不够用时就再扩展内存(通过 realloc() 重新分配)。Array 数组只能在尾部一个一个地插入(通过 push() 插入)或删除(通过 pop() 删除)元素。

我们通过重载过的[ ]运算符来访问数组元素,如果下标过小或过大,就会抛出异常(第53行代码);在抛出异常的同时,我们还记录了当前数组的长度和要访问的下标。

在使用 pop() 删除数组元素时,如果当前数组为空,也会抛出错误。

throw 用作异常规范

throw 关键字除了可以用在函数体中抛出异常,还可以用在函数头和函数体之间,指明当前函数能够抛出的异常类型,这称为异常规范(Exception specification),有些教程也称为异常指示符或异常列表。请看下面的例子:

double func (char param) throw (int);

这条语句声明了一个名为 func 的函数,它的返回值类型为 double,有一个 char 类型的参数,并且只能抛出 int 类型的异常。如果抛出其他类型的异常,try 将无法捕获,只能终止程序。

如果函数会抛出多种类型的异常,那么可以用逗号隔开:

double func (char param) throw (int, char, exception);

如果函数不会抛出任何异常,那么( )中什么也不写:

double func (char param) throw ();

如此,func() 函数就不能抛出任何类型的异常了,即使抛出了,try 也检测不到。

1、 虚函数中的异常规范
C++ 规定,派生类虚函数的异常规范必须与基类虚函数的异常规范一样严格,或者更严格。只有这样,当通过基类指针(或者引用)调用派生类虚函数时,才能保证不违背基类成员函数的异常规范。请看下面的例子:

class Base
{public:virtual int fun1(int) throw();virtual int fun2(int) throw(int);virtual string fun3() throw(int, string);
};
class Derived:public Base
{public:int fun1(int) throw(int);//错!异常规范不如 throw() 严格int fun2(int) throw(int);//对!有相同的异常规范string fun3() throw(string);//对!异常规范比 throw(int,string) 更严格
}

2、异常规范与函数定义和函数声明
C++ 规定,异常规范在函数声明和函数定义中必须同时指明,并且要严格保持一致,不能更加严格或者更加宽松。

请看下面的几组函数:

//错!定义中有异常规范,声明中没有
void func1();
void func1() throw(int) { }
//错!定义和声明中的异常规范不一致
void func2() throw(int);
void func2() throw(int, bool) { }
//对!定义和声明中的异常规范严格一致
void func3() throw(float, char*);
void func3() throw(float, char*) { }

异常规范使用的时候要谨慎

异常规范的初衷是好的,它希望让程序员看到函数的定义或声明后,立马就知道该函数会抛出什么类型的异常,这样程序员就可以使用 try-catch 来捕获了。如果没有异常规范,程序员必须阅读函数源码才能知道函数会抛出什么异常。

不过这有时候也不容易做到。例如,func_outer() 函数可能不会引发异常,但它调用了另外一个函数 func_inner(),这个函数可能会引发异常。再如,您编写的函数调用了老式的库函数,此时不会引发异常,但是库更新以后这个函数却引发了异常。总之,异常规范的初衷实现起来有点困难,所以大家达成的一致意见是,最好不要使用异常规范。

异常规范是 C++98 新增的一项功能,但是后来的 C++11 已经将它抛弃了,不再建议使用。
另外,各个编译器对异常规范的支持也不一样,请看下面的代码:

#include <iostream>
#include <string>
#include <exception>
using namespace std;
void func()throw(char*, exception)
{throw 100;cout<<"[1]This statement will not be executed."<<endl;
}
int main()
{try{func();}catch(int){cout<<"Exception type: int"<<endl;}return 0;
}

在 GCC 下,这段代码运行到第 7 行时程序会崩溃。虽然 func() 函数中发生了异常,但是由于 throw 限制了函数只能抛出 char*、exception 类型的异常,所以 try-catch 将捕获不到异常,只能交给系统处理,终止程序。

在 Visual C++ 下,输出结果为Exception type: int,这说明异常被成功捕获了。在 Visual C++ 中使用异常规范虽然没有语法错误,但是也没有任何效果,Visual C++ 会直接忽略异常规范的限制,函数可以抛出任何类型的异常。

800-C++ throw(抛出异常)详解相关推荐

  1. Java中throw re_详解Java编程中throw和throws子句的使用方法

    java throw:异常的抛出程序可以用throw语句抛出明确的异常.throw语句的通常形式如下: throw throwableinstance; 这里,throwableinstance一定是 ...

  2. java throw与throws_基于Java中throw和throws的区别(详解)

    系统自动抛出的异常 所有系统定义的编译和运行异常都可以由系统自动抛出,称为标准异常,并且 Java 强烈地要求应用程序进行完整的异常处理,给用户友好的提示,或者修正后使程序继续执行. 语句抛出的异常 ...

  3. throws java_基于Java中throw和throws的区别(详解)

    系统自动抛出的异常 所有系统定义的编译和运行异常都可以由系统自动抛出,称为标准异常,并且 Java 强烈地要求应用程序进行完整的异常处理,给用户友好的提示,或者修正后使程序继续执行. 语句抛出的异常 ...

  4. Throw和Throws详解

    Throw和Throws详解 ​ 1.throw是什么?有什么用? ​ throw是java中关于异常的一种操作,如果在try{}catch{}中使用了throw,就代表自己书写的这个方法,可以自己 ...

  5. java里throws详细讲解,基于Java中throw和throws的区别(详解)

    系统自动抛出的异常 所有系统定义的编译和运行异常都可以由系统自动抛出,称为标准异常,并且 Java 强烈地要求应用程序进行完整的异常处理,给用户友好的提示,或者修正后使程序继续执行. 语句抛出的异常 ...

  6. java抛出异常的方法_Java抛出异常处理的示例详解

    这篇文章主要介绍了java 抛出异常处理的方法的相关资料,throws关键字通常被应用在声明方法时,用来指定可能抛出的异常,这里就讲下如何使用,需要的朋友可以参考下 java 抛出异常处理的方法 为了 ...

  7. Java面试题 详解 超全200题

    目录 一.类.对象.方法.变量 二.集合类 三.流 四.多线程 五.异常处理 六.JVM 七.HotSpot虚拟机中的7种垃圾收集器 八.Javaweb 一.类.对象.方法.变量 1.知识图谱 2.类 ...

  8. 89-Spring Cloud 微服务详解

    Spring Cloud 微服务详解 微服务架构: 在说明该架构之前,再次的说明互联网应用架构演进(虽然之前说明过了) 随着互联网的发展,用户群体逐渐扩大,网站的流量成倍增长,常规的单体架构已无法满足 ...

  9. AWS MVC 详解

    由于新工作是在AWS PaaS平台上进行开发,为不耽误工作,先整理一下AWS MVS的使用规范,快速上手.对AWS PaaS平台的相关介绍留到以后再来补充.本文几乎是对官方学习文档的整理,有遗漏的后补 ...

最新文章

  1. Genome Biology | 药物基因组学数据库
  2. android 退出程序解决内存释放so的问题
  3. IE与Firefox的CSS兼容
  4. 二叉搜索树(BST)?平衡二叉树(AVL)?
  5. Java 洛谷 P1424 小鱼的航程(改进版)
  6. SAP ABAP收货或者货物移动(MIGO,MB11,MB1A)在保存时候的增强点
  7. mysql 无论输入什么都是现实 not found_Java高频面试题及答案
  8. 一个笑话,关于哲人和普通人的
  9. 美国政府牵头6家公司开发新一代超算 与中国竞争
  10. 设置一段文字显示不同的颜色及大小等属性
  11. python flask web开发 可视化开发_python web开发之——Flask入门教程
  12. Oracle为什么装在XP系统,重装xp系统后oracle恢复方法
  13. 天线设计相关性能参数
  14. 计算机 word 节是什么,Word分节符的含义和使用方法详解-word技巧-电脑技巧收藏家...
  15. Julia学习04——函数
  16. 关于微信小程序的wx.request执行后sucess和fail的问题
  17. [2017纪中10-25]嘟噜噜 约瑟夫问题 递推
  18. 摸个鱼的功夫,就学会了MySQL的DATE_FORMAT函数
  19. python中计算梯度值
  20. HTML5期末大作业:英雄联盟网站设计——英雄联盟LOL(4页) HTML+CSS+JavaScript web期末网站设计大作业

热门文章

  1. https安全证书提示证书风险怎么办
  2. 三分钟了解如何准备简历面试
  3. 杭电2019多校第三场 HDU-6608 Fansblog(威尔逊定理+素数间隔+逆元)
  4. C#测试网络连接测试
  5. 第五章(第四节)论路由器重置
  6. vs X64汇编 LNK2001: unresolved external symbol mainCRTStartup
  7. C语言中打印图形问题
  8. android系统相机实时数据采集流程,Android摄像头获取实时数据+Demo
  9. h5 版活体检测、视频活体检测
  10. python练习题——文件的打开、读取、复写