iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >一篇文章带你了解C++中的异常
  • 347
分享到

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

2024-04-02 19:04:59 347人浏览 泡泡鱼
摘要

目录异常抛出异常基本操作自定义的异常类栈解旋异常接口声明异常变量的生命周期异常的多态c++的标准异常库编写自己的异常类总结异常 在c语言中,对错误的处理总是两种方法: 1,使用整型的

异常

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

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

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

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

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

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

抛出异常基本操作

c++处理异常的优点:

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

img

在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中是不能正确执行的,都不会报错。但是在Qtlinux下是可以正确执行的。

异常变量的生命周期

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++的标准异常库

img

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

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

#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的子类,不知道为啥。

总结

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

--结束END--

本文标题: 一篇文章带你了解C++中的异常

本文链接: http://www.lsjlt.com/news/140183.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 一篇文章带你了解C++中的异常
    目录异常抛出异常基本操作自定义的异常类栈解旋异常接口声明异常变量的生命周期异常的多态c++的标准异常库编写自己的异常类总结异常 在c语言中,对错误的处理总是两种方法: 1,使用整型的...
    99+
    2024-04-02
  • 一篇文章带你深入了解Java异常
    目录一.初识异常1.常见的异常类型<1>除以0<2>数组下标越界<3>访问null对象2.防御式编程<1>LBYL<2>E...
    99+
    2024-04-02
  • 一篇文章带你了解python异常基础
    目录1. 程序中的问题1.1 低级语法错误1.2 中介错误:代码存在隐性错误1.3 高级错误:软件面对不确定性的异常错误2. 捕捉异常2.1 基本异常捕捉语句2.2 带finally...
    99+
    2024-04-02
  • 一篇文章带你入门C++的异常处理
    目录一、背景二、C++ 异常处理三、抛出异常与捕获异常四、catch(...)的作用总结一、背景 程序运行时常会碰到一些异常情况,例如: 做除法的时候除数为 0; ...
    99+
    2024-04-02
  • 一篇文章带你了解C++的KMP算法
    目录KMP算法步骤1:先计算子串中的前后缀数组NextC++代码:步骤2:查找子串在母串中出现的位置。总结KMP算法 KMP算法作用:字符串匹配 例如母串S = “aaagoogle...
    99+
    2024-04-02
  • 一篇文章带你了解C++中的显示转换
    目录总结命名的强制类型转换: 形式: cast-name<type>(expression); type是强制转换的类型,expression是强制转换的值。如果...
    99+
    2024-04-02
  • 一篇文章带你了解常用的Maven命令
    目录1、创建 Maven 工程2、Maven 的常用命令①、compile:将Java 源程序编译成 class 字节码文件。②、test:测试,并生成测试报告③、mvn clean...
    99+
    2024-04-02
  • 一篇文章带你了解Python中的类
    目录1、类的定义2、创建对象3、继承总结1、类的定义 创建一个rectangle.py文件,并在该文件中定义一个Rectangle类。在该类中,__init__表示构造方法。其中,s...
    99+
    2024-04-02
  • 一篇文章带你了解C++(STL基础、Vector)
    目录STL基本概念STL六大组件STL中容器、算法、迭代器容器算法迭代器初识Vector容器Vector三大遍历算法Vector存放其他数据类型 Vector容器嵌套总结S...
    99+
    2024-04-02
  • 一篇文章带你了解C/C++的回调函数
    目录函数指针概念先来看一个Hello World程序然后,采用函数调用的形式来实现用函数指针的方式来实现函数指针数组回调函数概念标准Hello World程序将它修改成函数回调样式修...
    99+
    2024-04-02
  • 一篇文章带你了解XGBoost算法
    目录1. 什么是XGBoost1.1 XGBoost树的定义1.2 正则项:树的复杂度1.3 树该怎么长1.4 如何停止树的循环生成2. XGBoost与GBDT有什么不同3. 为什...
    99+
    2024-04-02
  • 一篇文章带你了解JavaScript-语句
    目录表达式语句复合语句和空语句复合语句空语句声明语句varfunction条件语句ifif/elseelse ifswitch循环whiledo/whileforfor/in跳转标签...
    99+
    2024-04-02
  • 一篇文章带你了解c++运算符重载
    目录友元函数重载:复合赋值Operator pairings自增自减运算符的重载c++20,spaceship operator总结友元函数 一种全局函数,可以在类里声明,其他地方定...
    99+
    2024-04-02
  • 一篇文章带你了解jQuery动画
    目录1.控制元素的显示与隐藏 show() hide()2.控制元素的透明度 fadeIn() fadeOut()3:控制元素的高度 slideUp() slideDown()总结 ...
    99+
    2024-04-02
  • 一篇文章带你了解JavaScript-对象
    目录创建对象对象直接量通过new创建对象原型Object.create()属性的查询和设置继承属性访问错误删除属性检测属性序列化对象总结创建对象 对象直接量 对象直接量是由若干名/值...
    99+
    2024-04-02
  • 一篇文章带你了解C++特殊类的设计
    目录设计一个类,只能在堆上创建对象设计一个类,只能在栈上创建对象设计一个类,不能被拷贝设计一个类,不能继承设计一个类,只能创建一个对象(单例模式)单例模式的概念单例模式的实现饿汉模式...
    99+
    2024-04-02
  • 一篇文章带你了解Java Stream流
    目录一、Stream流引入现有一个需求:1.用常规方法解决需求2.用Stream流操作集合,获取流,过滤操作,打印输出二、Stream流的格式三、获取流四、Stream流的常用方法方...
    99+
    2024-04-02
  • 一篇文章带你了解C语言的文件操作
    目录为什么使用文件什么是文件程序文件数据文件文件名文件的打开和关闭文件指针fopen和fclose函数文件的顺序读写总结为什么使用文件 我们在想既然是通讯录就应该把信息记录下来,只有...
    99+
    2024-04-02
  • 一篇文章带你了解C语言操作符
    目录一、操作符分类 二、算术操作符三、移位操作符1、左移操作符 2、右移操作符2.1算术移位 2.2逻辑移位 四、位操作符 1、按位...
    99+
    2024-04-02
  • 一篇文章带你了解C++智能指针详解
    目录为什么要有智能指针?智能指针的使用及原理RALLshared_ptr的使用注意事项创建多个 shared_ptr 不能拥有同一个对象shared_ptr 的销毁shared_pt...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作