【C++基础】自定义异常类与多重捕获

目录

  • 自定义异常类
    • 构建过程
    • 例:Vec3D类的数组下标越界的异常类
  • 捕获多种无关异常
    • 不同的异常的捕获
    • 捕获派生异常
    • 异常处理的次序
    • 例子:多重捕获异常类
    • catch块的参数类型可以不用引用类型吗?

自定义异常类

自定义异常类通常由exception或其后代类派生。这样我们就可以去override e.what() 函数。

构建过程

这里我们以构建一个Vec3D类的数组下标越界的异常类为例子。
在这里插入图片描述

自定义异常类过程:
1、找一个高级一点的类,继承一下
class RangeException : public out_of_range
2、定义一些能够记录异常问题的变量
size_t dimension;
int index;
3、由异常类来记录问题,所以要将变量放到异常类的构造函数中,
RangeException(size_t dimension, int index);
4、给父类加入提示信息,在异常类的构造函数的初始化列表上对父类的构造函数也进行初始化。
注意exception基类构造函数是不接受参数的,其派生类可以接受一个字符串参数,用来描述该异常
RangeException () : out_of_range(“Vec index error”) {};

例:Vec3D类的数组下标越界的异常类

task1:创建Vec3D类,用array保存向量成员
task2:创建RangeException类,定义构造函数
RangeException(std::size_t dimension,const int index)
task3:实现Vec3D::operator[](const int index)
当index越界时,抛出RangeException的对象
task4:在主函数创建Vec3D对象并调用[]制造越界问题,捕获异常并输出异常中的信息。

RangeException.h:

#pragma once#include <iostream>
#include <exception>
class RangeException : public std::exception {
private:std::size_t dimension{ 3 };int index{ 0 };
public:RangeException (std::size_t dimension,const int index) {this->dimension = dimension;this->index = index;}std::size_t getDimension() {return dimension;}int getIndex() {return index;}
};

Vec3D.h:
这里需要注意一个点:数组下标运算符不加&时,返回的是一个右值,不能通过数组下标运算符修改数组的值。加上&后,返回的就是一个左值了。

#pragma once
#include <array>
#include "RangeException.h"
class Vec3D {
private:std::array <double, 3> v{1.0,1.0,1.0};
public:Vec3D() = default;Vec3D(double x, double y, double z){v[0] = x;v[1] = y;v[2] = z;}double &operator [] (const int index) {if(index >=0 && index <= 2) {return v[index];}else {throw RangeException(3,index);}}};

当主函数为:

#include <iostream>
#include "Vec3D.h"
using namespace std;int main()
{Vec3D v1 {1.2,1.3,1.4};cout << v1[4];return 0;
}

可以看见,抛出了我们自定义的异常。
在这里插入图片描述
接下来我们捕获这个异常:
注意,此时我们是捕获exception基类类型的异常,由于RangeException是从exception继承下来的,所以我们能够捕获,但是我们没有对exception的what进行覆写,所以不会打印异常信息。

#include <iostream>
#include <exception>
#include "Vec3D.h"
using namespace std;int main()
{Vec3D v1 {1.2,1.3,1.4};try {cout << v1[4];}catch (exception & e) {cout << "Exception :" << e.what() << endl;}return 0;
}

在这里插入图片描述
接下来我们将exception转换为RangeException,再调用RangeException的成员函数进行异常信息查询。

int main()
{Vec3D v1 {1.2,1.3,1.4};try {cout << v1[4];}catch (exception & e) {cout << "Exception :" << e.what() << endl;if (typeid(e) == typeid(RangeException)) {auto r = dynamic_cast<RangeException &>(e);cout << "Vector Dimension :" << r.getDimension() << endl;cout << "Index: " << r.getIndex() << endl;}}return 0;
}

在这里插入图片描述

捕获多种无关异常

不同的异常的捕获

try块中的代码可能会抛出不同类型的异常:
注意,throw出去的是对象,这里我们创建的匿名对象。

class EA: public exception { };
class EB: public exception { };
class C {
public:void foo(int x) {if (x == 1)throw EA();else if (x == 2)throw EB();}
};

而一个catch块只能捕获一种异常:

int main() {C c { };try {c.foo(1);c.foo(2);} catch (EA& a) {cout << a.what() << endl;} catch (EB& b) {cout << b.what() << endl;}

捕获派生异常

派生异常类:

class MyException: public logic_error { };

catch参数类型为基类异常类型,则可以匹配:能捕获基类对象、也能捕获派生类对象

try {throw MyException(); // 抛出派生异常对象
} catch (logic_error& e) {  // catch参数为基类异常,但可以捕获所有派生类异常对象MyException* p = dynamic_cast<MyException*>(&e); // 转指针失败不会再抛异常if (p != nullptr)cout << p->what() << endl;elsecout << e.what() << endl;
}

之前也提到过:dynamic_cast(obj)

  1. 若转型失败且NewType是指针类型,则返回nullptr。
  2. 若转型失败且NewType是引用类型,则抛出std::bad_cast类型的异常

异常处理的次序

捕获异常的正确次序:

派生类的catch块在前、基类的catch块在后

这种写法是错误的:

// (a)
try {...
} catch (logic_error& e) {...
} catch (MyException& e) {...
}

例子:多重捕获异常类

task1:基于Vec3D类、RangeException异常类修改
1.1:将Vec3D的维数抽取出来
1.2:将RangeException改为继承 out_of_range
task2:添加ZeroException,当向量除以一个数为0时抛该异常
该异常应该继承runtime_error
task3:重载operator / () ,为Vec3D类添加标量除法(向量除以一个数)
当除数为0.0时抛异常。
根据IEEE 754 rules:
x > 0.0 : x/0.0 = INF
x < 0.0 : x/0.0 = -INF
0.0 / 0.0 = NaN

Vec3D.h:

#pragma once#include <array>
#include <string>
#include <cmath>
#include <limits>
#include "RangeException.h"
#include "ZeroException.h"
class Vec3D {
public:constexpr static std::size_t DIMENSION = 3;private:std::array <double, DIMENSION> v{1.0,1.0,1.0};bool AreSame(double a, double b) {return std::fabs(a - b) < std::numeric_limits<double>::epsilon();}
public:Vec3D() = default;Vec3D(double x, double y, double z){v[0] = x;v[1] = y;v[2] = z;}double &operator [] (const int index) {if(index >=0 && index < DIMENSION) {return v[index];}else {throw RangeException(DIMENSION,index);}}Vec3D operator /(const double divisor) {//构造当前对象的拷贝Vec3D t(*this);if (AreSame(divisor,0.0))throw ZeroException();for (auto &i : t.v) {i /= divisor;}return t;}};

RangeException.h:

#pragma once#include <iostream>
#include <exception>
class RangeException : public std::out_of_range {
private:std::size_t dimension{ 0 };int index{ 0 };
public:RangeException (std::size_t dimension,const int index) : out_of_range("index exceeds Vector dimension"){this->dimension = dimension;this->index = index;}std::size_t getDimension() {return dimension;}int getIndex() {return index;}
};

ZeroException.h:

#pragma once#include <stdexcept>
#include <exception>class ZeroException : public std::runtime_error {
public:ZeroException() : runtime_error("Divided by 0.0") {};ZeroException(const char* msg) : runtime_error("Divided by 0.0") {};
};

main.cpp:

#include <iostream>
#include <exception>
#include "RangeException.h"
#include "Vec3D.h"
using namespace std;int main()
{Vec3D v1 {1.2,1.3,1.4};try {cout << (v1 / 0.0)[0] << endl;}catch (RangeException & e) {cout << "Exception :" << e.what() << endl;cout << "Vector Dimension :" << e.getDimension() << endl;cout << "Index: " << e.getIndex() << endl;}catch (ZeroException & e) {cout << "Exception :" << e.what() << endl;}return 0;
}

效果:
在这里插入图片描述

catch块的参数类型可以不用引用类型吗?

1、catch () 括号中的异常对象参数可否不用引用类型?
2、catch () 括号中的异常对象参数可否使用指针类型,比如: catch (Exception* e)
3、在多重异常捕获的代码中,若几个catch()括号中的参数是某个类继承链中不同层次的类的对象,此时括号中的参数可否不用引用类型?为什么?

1、catch()括号中的异常对象参数 可以使用:

1对象指针 catch(Exception * o)2对象引用catch(Exception & o)3一个对象catch(Exception  o) < >

2、 在多重异常捕获的代码中,若几个catch()括号中的参数是某个继承链中不同层次的类的对象,此时括号中的参数可以不用引用或指针类型,编译可以通过。

catch()参数如果是对象会发生什么?

1、会发生数据成员丢失

继承链上多态polymophic(虚函数的动态绑定)只有使用对象引用、指针类型才能发生。
当一个子类对象赋值给一个父类对象,这时放生的是对象之间数据成员间的拷贝,如果子类中比父类多出了一些数据成员,多出的数据成员会被截断丢弃,并且通过被赋值后的父类对象调用的虚方法只能是父类本身的虚方法,无法调用子类的虚方法,这是静态绑定。

2、会发生浅拷贝

形参与实参两个异常类对象的对象指针数据成员,指向堆中同一个new出来对象。
发生拷贝构造之后,原来作为catch()函数实参的异常对象要被销毁(先析构再收回空间),异常对象中对象指针指成员指向堆中new出来的对象地址空间被收回。但是作为catch()函数形参的异常对象还在,当形参离开catch块作用域范围前也需要被析构,并其离开catch块时将形参异常对象POP从栈中弹出释放空间。而在形参异常对象被析构时,需要先delete已经在堆中已经回收的对象地址,所以删除一个不存在的地址程序会出错。

3、·作为throw出的异常应该是对象,有可能被catch形参及块内数据覆盖:

在函数作用域范围内,被抛出的异常对象作为本地变量,本地变量存储在栈中,栈存储规律是先入后出,并且地址的大小排列顺序由高向低,而栈帧寻址方式重栈顶(低地址)到栈底(高地址)。

Throw抛异常是逐渐出栈过程,当遇到匹配的catch函数时停止出栈,转入catch作用域中。

也就是说throw出的异常对象高悬在栈顶,但是异常对象的空间已经被收回(对象已经POP出栈)。

但是异常对象存储的数据成员还存在与栈中,由与栈的写入数据序是有高向低地址,被throw抛出的异常对象地址在栈顶最小地址,所以很难被其他数据覆盖。

在catch函数作用域中只要抓取到被throw抛出的异常对象地址,就可以使用这个异常对象中的数据成员。

如果catch函数的形式参数如果是对象,形参对象也要入栈分配空间,这有可能会导致被throw抛出的异常对象数据被覆盖,所以使用对象指针或引用作为catch()函数的形参比较安全,指针或引用占用栈的空间比较小

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/377416.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

gprs 睡眠模式_GPRS的完整形式是什么?

gprs 睡眠模式GPRS&#xff1a;通用分组无线业务 (GPRS: General Packet Radio Service) GPRS is an abbreviation of General Packet Radio Service. It is a non-voice, high-level speed packet switching technology planned for GSM networks. On 2G and 3G cellular tran…

int main(int argc,char* argv[])讲解

分类&#xff1a; 学习笔记2011-11-07 21:502354人阅读评论(0)收藏举报dos编译器pathunixcommandc在最近学习中老是遇到 int main(int argc,char* argv[])&#xff0c;以为就是简单的参数应用了&#xff0c;但是看代码是没能理解参数的具体传递过程&#xff0c;上网…

Maven实战(七)——常用Maven插件介绍(上)

我们都知道Maven本质上是一个插件框架&#xff0c;它的核心并不执行任何具体的构建任务&#xff0c;所有这些任务都交给插件来完成&#xff0c;例如编译源代码是由maven-compiler-plugin完成的。进一步说&#xff0c;每个任务对应了一个插件目标&#xff08;goal&#xff09;&a…

【设计模式之美】<Reading Notes>抽象类与接口

抽象类特性 1、抽象类不允许被实例化&#xff0c;只能被继承。 2、抽象类可以包含属性和方法。方法既可以包含代码实现&#xff0c;也可以不包含代码实现。不包含代码实现的方法叫做抽象方法。 3、子类继承抽象类&#xff0c;必须实现抽象类中的所有抽象方法。 接口特性 1、…

多线程之间共享数据的实现

1&#xff1a;如果每个线程执行的代码相同&#xff0c;可以使用同一个Runnable对象&#xff0c;然后将共享的数据放在Runnable里面&#xff0c;来实现数据的共享。 例如买票系统... package com.cn.gbx;import java.util.Date; import java.util.Random; import java.util.Time…

AIX的完整形式是什么?

AIX&#xff1a;高级交互式主管 (AIX: Advanced Interactive Executive) AIX is an abbreviation of "Advanced Interactive Executive". AIX是“ Advanced Interactive Executive”的缩写 。 It is a progression sequence of proprietary UNIX operating systems …

c#生成随机字符串 用做批量申请账号时的随机密码还是相当不错的

//随机字符串生成器的主要功能如下&#xff1a; //1、支持自定义字符串长度 //2、支持自定义是否包含数字 //3、支持自定义是否包含小写字母 //4、支持自定义是否包含大写字母 //5、支持自定义是否包含特殊符号 //6、支持自定义字符…

【C++基础】C++11的noexcept声明符 与 异常传播

目录C noexcept&#xff1a;1、用途2、用法1、noexcept声明符的用法&#xff1a;2、noexcept运算符的用法异常传播1、异常传播的定义2、异常传播中的规则3、异常传播的代价C noexcept&#xff1a; 1、用途 C11使用noexcept指明函数是否抛出异常&#xff1a; 若函数不抛异常&a…

使用IndexReader.repen提高搜索速度

1,使用indexreader创建indexsearcher. 2,indexsearcher在使用完了以后不要关闭. 3.使用indexreader.isCurrent()判断索引是否被indexwriter改动. 4,如果索引被改动,indexsearcher.close()先前那个,然后new indexsearcher(indexreader). 传string给searcher,searcher会维护一个内…

CSS中的文本格式

CSS文字格式 (CSS text formatting) CSS text properties allow you to style your text in various ways very easily. Such as color, alignment, spacing, direction, etc. CSS文本属性使您可以轻松地以各种方式设置文本样式。 例如颜色 &#xff0c; 对齐方式 &#xff0c;…

【C++基础】重抛异常与异常的使用场景

重抛异常 异常处理程序可以重新抛出异常。 当它无法处理该异常&#xff0c;或想通知它的调用者发生了一个异常&#xff0c;此时就需要重抛异常&#xff1a; 1、抛出捕获的异常 try {// statements; } catch (TheException &ex) {// Do something;throw; }2、重新抛出另一…

vi @-function

vi 的功能 vi 是一个越用越强大的东西 功能&#xff1a; 例&#xff1a; 1 在插入模式 cwgadfly CTL-V ESC 看到的似&#xff1a; cwgadfly^[ 2 保存到g缓冲区 ESC :退出插入模式 "gdd :"g 指缓冲去个 dd删除一行 这样g缓冲去的内容是 cwgadflayESC 3 test love u 在…

CSS简写指南

1.margin 1.1 margin:1px 2px 3px(上 左右 下) 1.2 margin:2px 3px(上下 左右) 1.2 margin:1px 3px 2px 3px(上右下左) 2.padding(同上) 3.border border:1px red solid (border-width border-color border-style) 1 2 3border-width&#xff1a;1px 2px 3px; //最多可用四个值…

【C++基础】模板基础与函数模板

目录初识模板函数模板函数模板实例化显式实例化隐式实例化初识模板 求两个int、float、char类型的数据的最大值&#xff1a; C里面要这样写&#xff1a; int maxInt(int x, int y); double maxDouble(double x, double y); char maxChar(char x, char y);C使用函数重载&#…

scala 函数中嵌套函数_Scala合成函数

scala 函数中嵌套函数Scala中的合成功能 (Composition function in Scala) Scala composition function is a way in which functions are composed in program i.e. mixing of more than one functions to extract some results. In Scala programming language, there are mu…

js--基础

js 0为false 非0为true null为false 非null为true js 特有with(对象){}:可以确定对象所使用的范围。for(变量 in 对象)对变量和和行为进行遍历html xhtml xml &#xff1a;这些都是标记型文档。DOM:document object model 文档对象模型。 dom三层模型&#xff1a; dom1:将…

字符串的处理[C#]

//string Str1 "友情相逢"; //string Str2 "用一生爱你"; //#region char的使用 //char a a; //Console.WriteLine("IsLetter方法判断a是否为字母&#xff1a;{0}", Char.IsLetter(a)); …

CentOS安全设置

CentOS安全设置 删除多余的用户和用户组&#xff0c;修改口令文件属性&#xff0c;禁止[CtrlAltDelete]重启命令&#xff0c;防止别人ping的方法。整理自互联网。1、删除多余的用户和用户组//删除多余用户# vi /etc/passwduserdel admuserdel lpuserdel syncuserdel shutdownus…

【设计模式之美】<Reading Notes>继承与组合

继承缺点 继承是面向对象的四大特性之一&#xff0c;用来表示类之间的 is-a 关系&#xff0c;可以解决代码复用的问题。虽然继承有诸多作用&#xff0c;但继承层次过深、过复杂&#xff0c;也会影响到代码的可维护性。在这种情况下&#xff0c;我们应该尽量少用&#xff0c;甚至…

scala中何时使用下划线_在Scala中使用下划线

scala中何时使用下划线Underscore (_) character is reserved in Scala and has multiple usages in the programming language. Based on functions that use the underscore have the following usages: 下划线(_)字符在Scala中保留&#xff0c;并且在编程语言中有多种用法。…