一篇文章带你了解C++中的异常

目录
  • 异常
    • 抛出异常基本操作
    • 自定义的异常类
    • 栈解旋
    • 异常接口声明
    • 异常变量的生命周期
    • 异常的多态
    • c++的标准异常库
    • 编写自己的异常类
  • 总结

异常

在c语言中,对错误的处理总是两种方法:

1,使用整型的返回值表示错误(有时候用1表示正确,0表示错误;有的时候0表示正确,1表示错误)

2,使用errno宏(可以简单理解为一个全局整形变量)去记录错误。(如果错误,就将被改变的全局整形变量返回)

c++中仍然可以用上面的两种方法,但是有缺点。

(1)返回值不统一,到底是1表示正确,还是0表示正确。

(2)返回值只有一个,通过函数的返回值表示错误代码,那么函数就不能返回其他的值。(输出的值到底是表示异常的值-1,还是最后在那个结果就是-1呢)

抛出异常基本操作

c++处理异常的优点:

异常处理可以带调用跳级。

在C程序中出现了异常,返回值为-1。如果C直接将-1传给B,不进行处理(也不给B报错),那么B收到-1返回值以后就会进行自己的处理,然后返回给A,然后A再进行自己的处理,那么最终程序返回的值肯定是错误的。

所以在c++中,要求必须要处理异常。如果C处理不了允许抛给B处理,B处理不了也允许抛给A处理,如果A也处理不了,那么就直接终止代码报错。

int myDivision(int a, int b)
{
	if (b == 0)
	{
		throw -1;//抛出-1
	}
	else
		return 1;
}
int main()
{
	int a = 10;
	int b = 0;
	try
	{
		myDivision(a, b);
	}
	catch (int)
	{
		cout << "int类型异常捕获" << endl;
	}
	return 0;
}

如果抛出来的是char类型的数据(异常),那么就需要有个char类型的接收处理代码(catch+类型)。

除了int,char,double以外的抛出类型,可以用...来接收。

catch (...)
	{
		cout << "其他类型异常捕获" << endl;
	}

如果捕获到了异常,但是不想处理,那么可以继续向上抛出异常。

int myDivision(int a, int b)
{
	if (b == 0)
	{
		throw -1;
	}
}
void test()
{
	int a = 10;
	int b = 0;
	try
	{
		myDivision(a, b);
	}
	catch (int)
	{
		throw;
	}
}
int main()
{
	try
	{
		test();
	}
	catch (int)
	{
		cout << "int类型异常捕获" << endl;
	}
	return 0;
}

自定义的异常类

注意:类名加()就是匿名对象

class MyException
{
public:
	void printError()
	{
		cout << "我自己的异常" << endl;
	}
};
int myDivision(int a, int b)
{
	if (b == 0)
	{
		throw  MyException();//类名加()就是匿名对象,抛出的就是匿名对象。
	}
}
int main()
{
	int a = 10;
	int b = 0;
	try
	{
		myDivision(a, b);
	}
	catch (MyException e)
	{
		e.printError();//可以直接用这个对象来调用成员函数
	}
	return 0;
}

总结:

1,c++中如果出现异常,不像c中return -1,而是直接throw -1,然后后面再用try catch进行处理。

2,可能出现异常的地方使用try

3,如果与抛出的异常匹配的处理没有找到,那么运行函数terminate将被自动调用,其缺省功能调用abort终止程序。

栈解旋

从try代码行开始 到 throw将代码抛出去之前。所有栈上的数据会被自动的释放掉。

释放的顺序和创建的顺序是相反的。(栈:先进后出)

class Person
{
public:
	Person()
	{
		cout << "Person的默认构造调用" << endl;
	}
	~Person()
	{
		cout << "Person的析构调用" << endl;
	}
};
int myDivision(int a, int b)
{
	if (b == 0)
	{
		Person p1;
		Person p2;
		throw  Person();//匿名对象
	}
}
int main()
{
	int a = 10;
	int b = 0;
	try
	{
		myDivision(a, b);
	}
	catch (Person)
	{
		cout << "拿到Person类异常,正在处理" << endl;
	}
	return 0;
}

输出结果:

Person的默认构造调用
Person的默认构造调用
Person的默认构造调用
Person的析构调用
Person的析构调用
拿到Person类异常,正在处理
Person的析构调用

在throw之前创建了两个对象,并抛出一个匿名对象。发现在抛出去之前,两个对象就被释放了。然后抛出去的对象在程序结束时候释放。这就是栈解旋

异常接口声明

只允许抛出规定类型的异常。

//异常接口的声明
void func() throw(int , double)//只允许抛出int和double类型的异常。
{
	throw 3.14;
}
int main()
{
	try
	{
		func();
	}
	catch (int)
	{
		cout << "int类型异常捕获" << endl;
	}
	catch (...)
	{
		cout << "其他类型异常捕获" << endl;
	}
	return 0;
}

throw()的意思就是不允许抛出异常。

这个代码在VS中是不能正确执行的,都不会报错。但是在QT和linux下是可以正确执行的。

异常变量的生命周期

class MyException
{
public:
	MyException()
	{
		cout << "MyException的默认构造调用" << endl;
	}
	MyException(const MyException&e)
	{
		cout << "MyException的拷贝构造调用" << endl;
	}
	~MyException()
	{
		cout << "MyException的析构调用" << endl;
	}
};
void doWork()
{
	throw MyException();//抛出匿名对象
}
int main()
{
	try
	{
		doWork();
	}
	catch (MyException e)
	{
		cout << "自定义异常的捕获" << endl;
	}
	return 0;
}

运行的结果:

MyException的默认构造调用
MyException的拷贝构造调用
自定义异常的捕获
MyException的析构调用
MyException的析构调用

throw匿名对象的时候创建了对象,所以用默认构造。

用MyException e来接收对象的时候,是用的值来接收的,所以会调用拷贝构造函数。

然后就打印,并且将两个对象删除掉。

这样效率不高,如果接收对象的时候不用值来接收,而是用引用来接收,这样就能少调用一次的拷贝构造和一次析构函数。

catch (MyException &e)
	{
		cout << "自定义异常的捕获" << endl;
	}

运行结果:

MyException的默认构造调用
自定义异常的捕获
MyException的析构调用

还有一种方式,就是将匿名函数的地址穿进来,这样也不需要调用析构函数。

class MyException
{
public:
	MyException()
	{
		cout << "MyException的默认构造调用" << endl;
	}
	MyException(const MyException&e)
	{
		cout << "MyException的拷贝构造调用" << endl;
	}
	~MyException()
	{
		cout << "MyException的析构调用" << endl;
	}
};
void doWork()
{
	throw & MyException();//抛出匿名对象
}
int main()
{
	try
	{
		doWork();
	}
	catch (MyException *e)
	{
		cout << "自定义异常的捕获" << endl;
	}
	return 0;
}

运行结果:(其实没有运行成功)

MyException的默认构造调用
MyException的析构调用
自定义异常的捕获

如果传的是指针,那么匿名对象很快就会释放掉(匿名对象的特点就是执行完就释放掉),最终得到了指针也没有办法进行操作。

但如果匿名对象在=的右边,且左边还给这个对象起名了(如同上面的传对象,引用接收),那么匿名对象的寿命就会延续到左边的变量上。如果传的是指针,给指针起名和给对象起名不一样,所以就会释放。

如果不想被释放掉,还有一种方式,那就是将这个对象创建在堆区,等待着程序员自己去释放。(不会调用析构)

void doWork()
{
	throw new MyException();//抛出匿名对象
}

异常的多态

//异常的基类
class BaseException
{
public:
	virtual void printError() = 0;//纯虚函数
};
//空指针异常
class NULLPointerException:public BaseException
{
public:
	virtual void printError()
	{
		cout << "空指针异常" << endl;
	}
};
//越界异常
class outOfRangeException :public BaseException
{
public:
	virtual void printError()
	{
		cout << "越界异常" << endl;
	}
};
void doWork()
{
	//throw NULLPointerException();
	throw outOfRangeException();
}
int main()
{
	try
	{
		doWork();
	}
	catch (BaseException &e)//用父类的引用接收子类的对象
	{
		e.printError();
	}
	return 0;
}

提供一个基类的异常类,其中有个纯虚函数(有可能是虚函数),然后子类重写。

调用的时候,用父类的引用来接收子类的对象就可以,这样就实现了异常的多态。抛出的是什么类的对象,那么就会调用什么类的函数。

c++的标准异常库

标准库中提供了很多的异常类,它们是通过类继承组织起来的。

如果使用系统提供的标准异常的时候,需要调用规定的头文件

#include <stdexcept>std:标准 except:异常

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
#include<string>
#include<stdexcept>
class Person
{
public:
	Person(int age)
	{
		if (age < 0 || age>150)
		{
			throw out_of_range("年龄必须在0-150之间");
		}
	}
	int m_age;
};
int main()
{
	try
	{
		Person p(151);
	}
	catch (out_of_range&e)
	{
		cout << e.what() << endl;//what函数是获得字符串中的内容
	}
	return 0;
	//如果使用多态:(异常子类的名字太难记,不好写)
	//catch (exception &e)
}

自己平时不会主动调用系统的标准异常。在写的系统提供的异常后面加()的字符串然后在接收的时候用父类的引用接收,然后用这个引用e的what函数就可以找到这个字符串。

编写自己的异常类

标准异常类是优先的,可以自己编写异常类。

和上面自己写的MyException不太一样。给系统提供的派生类exception提供儿子(需要重写父类的函数等)

ps:在非静态成员函数后面加const,表示成员函数隐含传入的this指针为const指针,决定了在该成员函数中,任意修改它所在的类的成员操作是不允许的。

经过考察上面的有关out_of_range的代码可得:抛出的是out_of_range类的一个对象,接收的时候也是用引用e来接收的这个对象。然后这个引用可以调用what()的函数来返回一个字符串,这个字符串正好是创建out_of_range对象的时候待用有参函数要传入的 字符串。

所以,自己写的out_of_range类一定要有个有参构造,参数就是字符串,然后还有个what的重写函数,需要返回这个字符串,这个字符串作为属性。

ps:注意:const char*可以隐式转换为string,但是反过来就不成立。

所以如果要使得string转换成const char*,需要调用string中的成员函数函数.c_str()

const char* what() const
{
    string s;
    return s.c_str();
    //返回的就是const char*了。
}

完整代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
#include<string>
#include<stdexcept>
class MyOutOfRangeException:public exception//先继承一下这个父亲
{
	//到底要重写什么呢?点开exception以后,发现有两个virtual的虚函数,一个析构,还有一个what,析构不需要重写
	//所以需要重写what函数。
public:
	MyOutOfRangeException(const char* str)
	{
		//const char*可以隐式类型转换为string 反之不可以
		this->m_myerrorString = str;
	}
	//可以再重载一下这个函数,使得接收的参数改为string类型
	MyOutOfRangeException(string str)
	{
		this->m_myerrorString = str;
	}
	virtual char const* what() const
	{
		return m_myerrorString.c_str();//加了.c_str就可以返回const char*了
	}
	string m_myerrorString;//字符串属性
};
class Person
{
public:
	Person(int age)
	{
		if (age < 0 || age>150)
		{
			throw MyOutOfRangeException("年龄必须在0-150之间");//const char*
			throw MyOutOfRangeException(string("年龄必须在0-150之间"));//string,返回的是string类的匿名对象
		}
		else
		{
			this->m_age = age;
		}
	}
	int m_age;
};
int main()
{
	try
	{
		Person p(1000);
	}
	catch (MyOutOfRangeException e)//用exception也可以,证明创建的这个类确实是exception的子类。
	{
		cout << e.what() << endl;
	}
	return 0;
}

但是最后发现好像没有成功的将MyOutOfRangeException手写异常类变成exception的子类,不知道为啥。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • C++学习笔记之浅谈异常处理

    异常处理主要是针对能通过编译但是运行是在某个特定条件下会出现异常,程序崩溃,结果出错.来进行的东西 C++处理异常的机制是由3个部分组成的,即检查(try).抛出(throw)和捕捉(catch).把需要检查的语句放在try块中,throw用来当出现异常时发出一个异常信息,而catch则用来捕捉异常信息,如果捕捉到了异常信息,就处理它. try {被检查的语句} catch(异常信息类型 [变量名]) {进行异常处理的语句} 粘一个简单的异常处理的题: 如果三角形满足三角形内角和大于第三边才会有

  • C++中的异常实例详解

    目录 1.异常 1.1C语言中处理错误的方式 1.2C语言处理错误方式的缺陷 2.C++异常 2.1异常相关关键字 2.2异常的使用 2.2.1异常的抛出和匹配原则 2.3异常的重新抛出 2.4自定义异常体系 2.5异常安全 2.6异常规范 2.7C++标准库的异常体系 2.8异常的优缺点 2.8.1优点 2.8.2缺点 总结 1. 异常 异常: 异常是面向对象与法处理错误的一种方式 1.1 C语言中处理错误的方式 返回错误码 (很多API接口都会把错误码放到errno当中) 终止程序 (ass

  • 深入了解C++异常处理

    目录 基本的异常处理 怎么抛出异常 捕获和处理异常 不存在异常的描述 --- 标识性作用     删减符 ... 异常处理中的传参操作  --- 可以写一个变量进去 可以抛出自己类的对象 标准库当中的异常类 引发标准库中内存申请失败的异常 基本的异常处理 异常处理机制:暂缓问题处理,不在当前函数中处理,在他的调用者中处理(先上车,后补票) 什么是异常:任何东西都可以认为是异常,错误只是异常的一种 异常一旦被抛出,不做处理,如果引发异常,会调用默认abort函数终止程序 捕获和处理异常: thro

  • 详解c++中的异常

    一.什么是异常处理 一句话:异常处理就是处理程序中的错误. 二.为什么需要异常处理,异常处理的基本思想 C++之父Bjarne Stroustrup在<The C++ Programming Language>中讲到:一个库的作者可以检测出发生了运行时错误,但一般不知道怎样去处理它们(因为和用户具体的应用有关):另一方面,库的用户知道怎样处理这些错误,但却无法检查它们何时发生(如果能检测,就可以再用户的代码里处理了,不用留给库去发现). Bjarne Stroustrup说:提供异常的基本目的

  • C++异常处理入门(try和catch)

    目录 捕获异常 发生异常的位置 开发程序是一项"烧脑"的工作,程序员不但要经过长期的知识学习和思维训练,还要做到一丝不苟,注意每一个细节和边界.即使这样,也不能防止程序出错. 专家指出,长期作息不规律 + 用脑过度的危害很大,可能会诱发神经衰弱.失眠等疾病.我就是受害者之一,曾被失眠困扰了好几年,不但入睡困难,还容易早醒.程序员要注意劳逸结合,多去健身房,多跑步,多打球,多陪女朋友旅游等,千万不要熬夜,以为深夜写代码效率高,这样会透支年轻的身体. 程序的错误大致可以分为三种,分别是语法

  • 一篇文章带你了解C++中的异常

    目录 异常 抛出异常基本操作 自定义的异常类 栈解旋 异常接口声明 异常变量的生命周期 异常的多态 c++的标准异常库 编写自己的异常类 总结 异常 在c语言中,对错误的处理总是两种方法: 1,使用整型的返回值表示错误(有时候用1表示正确,0表示错误:有的时候0表示正确,1表示错误) 2,使用errno宏(可以简单理解为一个全局整形变量)去记录错误.(如果错误,就将被改变的全局整形变量返回) c++中仍然可以用上面的两种方法,但是有缺点. (1)返回值不统一,到底是1表示正确,还是0表示正确.

  • 一篇文章带你吃透JavaScript中的DOM知识及用法

    目录 一.前言 二.DOM框架 三.认识DOM节点 四.JS访问DOM 1.获取节点 2.改变 HTML 3.改变 CSS 4.检测节点类型 5.操作节点间的父子及兄弟关系 6.操作节点属性 7.创建和操作节点 总结 一.前言 DOM:Document Object Model(文档对象模型),定义了用户操作文档对象的接口,可以说DOM是自HTML将网上相关文档连接起来后最伟大的创新.它使得用户对HTML有了空前的访问能力,并使开发者将HTML作为XML文档来处理. 本文知识导图如下: 二.DO

  • 一篇文章带你了解Java中ThreadPool线程池

    目录 ThreadPool 线程池的优势 线程池的特点 1 线程池的方法 (1) newFixedThreadPool (2) newSingleThreadExecutor (3) newScheduledThreadPool (4) newCachedThreadPool 2 线程池底层原理 3 线程池策略及分析 拒绝策略 如何设置maximumPoolSize大小 ThreadPool 线程池的优势 线程池做的工作主要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些

  • 一篇文章带你了解数据库中group by的用法

    前言 本章主要介绍数据库中group by的用法,也是我们在使用数据库时非常基础的一个知识点.并且也会涉及Join的使用,关于Join的用法,可以看我写的上一篇文章:带你了解数据库中JOIN的用法 如有错误还请大家及时指出~ 以下都是采用mysql数据库 Group By 概念 Group By语句从英文的字面意义上理解就是"根据(by)一定的规则进行分组(Group)". 作用:通过一定的规则将一个数据集划分成若干个小的区域,然后针对若干个小区域进行数据处理. 注意:group by

  • 一篇文章带你了解数据库中JOIN的用法

    前言 本章主要介绍数据库中Join的的用法,也是我们在使用数据库时非常基础的一个知识点.本次会介绍数据库中的 inner join. left join. right join 的用法以及它们之间的区别. 文章如有错误还请大家及时指出~ 以下都是采用mysql数据库 Join 相信大家在学习数据库的使用时,都有使用过Join,对数据库中的两张或两张以上表进行连接操作. Join 分为: 内连接(inner join) 外连接(outer join) 其中外连接分为: 左外连接(left oute

  • 一篇文章带你了解Python中的装饰器

    目录 前言 Python 中的装饰器是什么 语法糖 使用 Python 装饰器修改函数行为 使用 Python 装饰器对函数进行计时 使用 Python 装饰器将有用信息记录到终端 Web app 中使用的装饰器 将参数传递给 Python 装饰器 使用多个 Python 装饰器 总结 前言 本文将带你学习装饰器在 Python 中的工作原理,如果在函数和类中使用装饰器,如何利用装饰器避免代码重复(DRY 原则,Don’t Repeat Yourself ). Python 中的装饰器是什么 装

  • 一篇文章带你了解Java 中序列化与反序列化

    目录 一. 序列化和反序列化概念 二. 序列化和反序列化的必要性 三. 序列化和反序列化的实现 1. JDK类库提供的序列化API 2. 实现序列化的要求 3. 实现Java对象序列化与反序列化的方法 4. JDK类库中序列化的步骤 5. JDK类库中反序列化的步骤 四.序列化的必要条件 五.序列化高级,使用情境分析 1. 序列化ID问题 特性使用案例 2. 静态变量序列化 3. 父类的序列化与 Transient 关键字 4. 对敏感字段加密 5. 序列化存储规则 总结 一. 序列化和反序列化

  • 一篇文章带你了解C++中的显示转换

    目录 总结 命名的强制类型转换: 形式: cast-name<type>(expression); type是强制转换的类型,expression是强制转换的值.如果type是引用类型,则结果是左值.case-name是C++四种转换类型static_cast.dynamic_cast.const_cast和reinterpret_cast的一种. static_cast 可以被用于强制隐形转换(例如,non-const对象转换为const对象,int转型为double,等等)作用于对象,它还

  • 一篇文章带你了解Python中的类

    目录 1.类的定义 2.创建对象 3.继承 总结 1.类的定义 创建一个rectangle.py文件,并在该文件中定义一个Rectangle类.在该类中,__init__表示构造方法.其中,self参数是每一个类定义方法中的第一个参数(这里也可以是其它变量名,但是Python常用self这个变量名).当创建一个对象的时候,每一个方法中的self参数都指向并引用这个对象,相当于一个指针.在该类中,构造方法表示该类有_width和_height两个属性(也称作实例变量),并对它们赋初值1. __st

  • 一篇文章带你了解python中的typing模块和类型注解

    目录 typing模块 Dict List Tuple set/AbstractSet Sequence NoReturn Any TypeVar NewType Callable Union Optional Generator 总结 function annotation 写法: 使用冒号 : 加类型代表参数类型 默认值参数示例:b: int = 2 使用 -> 加类型代表返回值类型 python解释器运行时并不会检查类型,类型不对也不会抛异常,仅仅是注解而已.示例: def plus(a:

随机推荐