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

2023-11-03

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++ 会直接忽略异常规范的限制,函数可以抛出任何类型的异常。
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

800-C++ throw(抛出异常)详解 的相关文章

随机推荐

  • Notes of Causal Inference Course by Brady Neal (Chap 1-5)

    本文是Youtube上causal inference课程系列的笔记 便于以后回顾 课程作者是Brady Neal 视频link在这里 这个lecture很长 但基本上看完Chapter 1和2就能大致知道causal inference的
  • DFT知识点扫盲——DFT概览

    IC设计公司 无论是研发 测试 PE 质量等岗位 对DFT的概念都不陌生 接下来的几篇内容 谈谈对DFT的理解 有聊得不对的地方 也请做DFT的同行 不吝赐教 1 什么是DFT DFT Design for Test 可测试性是一种设计属性
  • CMD(命令提示符)修改盘符

    进入命令提示符后 输入diskpart 进入diskpart exe list disk可以查询当前计算机磁盘 DISKPART gt list disk 磁盘 状态 大小 可用 Dyn Gpt 磁盘 0 联机 884 GB 1024 KB
  • [转]Tesseract-OCR学习系列(四)API

    原文地址 http www jianshu com p 3df039e42986 2016 09 20 Other API Examples 参考文档 https github com tesseract ocr tesseract wik
  • Python代码——卫星天空图绘制

    前期数据处理 33条消息 C 保存char int 和double到txt文件 他人是一面镜子 保持谦虚的态度的博客 CSDN博客 一 单系统代码 import math import matplotlib pyplot as plt f
  • 基于单片机的智能数字电子秤proteus仿真设计

    一 系统方案 1 当电子称开机时 单片机会进入一系列初始化 进入1602显示模式设定 如开关显示 光标有无设置 光标闪烁设置 定时器初始化 进入定时器模式 如初始值赋值 之后液晶会显示Welcome To Use Electronic Sc
  • 【统计学】stata 梳理输出命令逻辑关系 asdoc outreg2 logout esttab区别 优劣势

    一 概述 初学stata的时候对于stata输出的逻辑颇为疑惑 因为学python和cpp的时候输出函数就是那几个非常的简单 而statac的asdoc outreg2 logout 和 esttab 这些常见的命令在跳出来的时候往往分不清
  • 如何获得最新的太阳神三国杀 自己Qt编译

    太阳神三国杀 是一个基于C QT GUI框架的三国杀非官方开源软件 开发者 Moligaloo 开发者网站 http mogara org 可以在它上面体验一些不同的模式 尤其可以体验一些在测试服上运行 但却还未上线的新武将 相信有些小伙伴
  • postgresql 15源码浅析(1)—— postgres中的1号数据库

    摘要 在创建数据库集簇后 该集簇中默认会包含三个系统数据库template1 template0和postgres 其中template0和postgres都是在初始化过程中从template1复制出来的 这个理论大家想必不是那么陌生 但是
  • 医宗金鉴-01-伤寒论注

    from www oldzy com 老中医 见附件
  • 几个很实用的软件 root 改机 软改 硬改 改串号 改设备 参数生成器APK 电脑软件

    有没有好的安卓抹机软件或者改机 硬件信息修改器 Android改机 安卓改机软件 免root 不刷机 拒绝Xposed 实现 技术分析 改机软件排行榜大全 各种安卓抹机软件 手机抹机改机软件 机型修改器 一键新机改机软件全部免root破解下
  • vs2008中,在OCX控件中应用doc/view基本步骤

    1 利用向导创建一个MFC ActiveX Control控件CMyOCX 2 在工程中加入ActivDoc头文件和执行文件 class CActiveXDocTemplate public CSingleDocTemplate enum
  • 冒泡排序--python(详解)

    对于一个数组 4 6 3 9 第一轮 第一次比较 4 lt 6 两数不用进行交换 数组不变为 4 6 3 9 第二次比较 6 gt 3 两数进行交换 得到一个新数组 4 3 6 9 第三次比较 6 lt 9 两数不用进行交换 数组不变为 4
  • PHP Fatal error: Call to undefined function curl_init() in xxx

    找到php ini文件中这一行 本人是centos extension curl so 加上这一行 重启服务器 如果服务器报错说找不到curl so文件 下载php源码包http www php net downloads php tar
  • MyBaties 提示org.apache.ibatis.builder.BuilderException: The expression ‘’**' evaluated to a null val

    MyBaties 提示的错误信息 org apache ibatis builder BuilderException The expression evaluated to a null value 错误的原因 SQL 参数为List 集
  • java设计模式——状态模式

    状态模式的定义 一个对象在其内部状态改变的时候改变其行为 状态驱动 由上下文负责 代替if else 代替 switch case 普通方式 public String orderState String state if state eq
  • Percona-mysql server 5.5升级5.6

    http blog csdn net lqx0405 article details 50162557 系统环境 操作系统 CentOS 6 5 64 MySQL Percona server 5 5 5 6 一 升级的目的 为什么MySQ
  • Qt学习:Qt优雅地结束线程

    一 Qt线程 如果一个线程运行完成 就会结束 可很多情况并非这么简单 由于某种特殊原因 当线程还未执行完时 我们就想中止它 不恰当的中止往往会引起一些未知错误 比如 当关闭主界面的时候 很有可能次线程正在运行 这时 就会出现如下提示 QTh
  • noip2007 奖学金 (排序)

    A1159 奖学金 时间限制 1 0s 内存限制 256 0MB 总提交次数 797 AC次数 339 平均分 60 95 将本题分享到 查看未格式化的试题 提交 试题讨论 试题来源 NOIP2007 普及组 问题描述 某小学最近得到了一笔
  • 800-C++ throw(抛出异常)详解

    C throw 抛出异常 详解 抛出 Throw gt 检测 Try gt 捕获 Catch 异常必须显式地抛出 才能被检测和捕获到 如果没有显式的抛出 即使有异常也检测不到 在 C 中 我们使用 throw 关键字来显式地抛出异常 它的用