c++异常介绍

一 . C语言传统的处理错误的方式

1. 终止程序,如assert,缺陷:用户难以接受。如发生内存错误,除0错误时就会终止程序。
2. 返回错误码,缺陷:需要程序员自己去查找对应的错误。

二 . C++异常概念及使用

当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者处理这个错误。

throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
catch: 在您想要处理问题的地方,通过异常处理程序捕获异常.catch 关键字用于捕获异
           常,可以有多个catch进行捕获。
try: try 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块。

 

1.简单的代码使用

double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0)throw "Division by zero condition!";  //throw抛出异常elsereturn ((double)a / (double)b);
}
void Func()
{int len, time;cin >> len >> time;cout << Division(len, time) << endl;
}
int main()
{try {Func();}//catch捕获异常catch (const char* s)//与异常匹配的catch{cout << s << endl;}catch (char ch){cout << ch << endl;}catch (...) //捕获任意类型的异常{cout << "unkown exception" << endl;}
//如果throw 5,则打印unkown exceptionreturn 0;
}

2.异常的抛出和捕获

a.异常的抛出和匹配原则
1. 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。
2. 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。(在b里说)
3. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,
所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似
于函数的传值返回)
4. catch(...)可以捕获任意类型的异常,问题是不知道异常错误是什么。
5. 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,
使用基类捕获。(后面说)

解释:

b. 在函数调用链中异常栈展开匹配原则
1. 首先检查throw本身是否在try块内部,如果是,再查找匹配的catch语句。如果有匹配的,则
调到catch的地方进行处理。
2. 没有匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch。
3. 如果到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的
catch子句的过程称为栈展开。所以实际中我们最后都要加一个catch(...)捕获任意类型的异
常,否则当有异常没捕获,程序就会直接终止。
4. 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。

解释:

double Division(int a, int b)
{try {// 当b == 0时抛出异常if (b == 0)throw "Division by zero condition!";elsereturn ((double)a / (double)b);}catch (const char s){cout << s << endl;cout << "div()" << endl;}}
void Func()
{try{int len, time;cin >> len >> time;cout << Division(len, time) << endl;}catch (const char* s){cout << s << endl;cout << "func()" << endl;}
}
int main()
{try {Func();}catch (const char* s){cout << s << endl;cout << "main()" << endl;}catch (char ch){cout << ch << endl;}catch (...) {cout << "unkown exception" << endl;}return 0;
}

3. 异常的重新抛出

double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0){throw "Division by zero condition!";}return (double)a / (double)b;
}
void Func()
{// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。//出现内存泄漏// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再// 重新抛出去。int* array = new int[10];try {int len, time;cin >> len >> time;cout << Division(len, time) << endl;}catch (...){cout << "delete []" << array << endl;delete[] array;throw; //捕获了什么异常就抛什么异常}// ...cout << "delete []" << array << endl;delete[] array;
}
int main()
{try{Func();}catch (const char* errmsg){cout << errmsg << endl;}return 0;
}


 

4.异常安全

a . 构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不
完整或没有完全初始化
b . 析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏(内
存泄漏、句柄未关闭等)
c . C++中异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄漏,在lock和unlock之间抛出了异常导致死锁,C++经常使用RAII来解决以上题,关于RAII我们智能指针这节进行讲解。

 

5. 异常规范

说明:异常规范是期望,可以不按照规范去操作。

1. 异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。 可以在函数的
    后面接throw(类型),列出这个函数可能抛掷的所有异常类型。
2. 函数的后面接throw(),表示函数不抛异常。
3. 若无异常接口声明,则此函数可以抛掷任何类型的异常。

 

//c++98
// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);
// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);
// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();
// C++11 中新增的noexcept,表示不会抛异常
thread() noexcept;
thread (thread&& x) noexcept;

c++98 

void fun(int a) throw(const char*)//指可能抛的异常的类型
{if (a == 0){throw "a==0";//const char*类型}cout << a << endl;
}//还可以写成
//void fun(int a) throw(int)//可能抛的异常的类型是int
//void fun(int a) throw(const char*, int)
//是对我们的期望,并不是强行要求
int main()
{try{int a;cin >> a;fun(a);}catch (const char* s){cout << s << endl;}catch (...){cout << "未知的异常" << endl;}return 0;
}

c++11

void fun(int a) noexcept //指此函数没有异常
{if (a == 0){throw "a==0";}cout << a << endl;
}
//如果这里出现异常,因为noexcept声明没有异常,后面我们不会捕获
//最终导致程序终止。
int main()
{try{int a;cin >> a;fun(a);}catch (const char* s){cout << s << endl;}catch (...){cout << "未知的异常" << endl;}return 0;
}

三 . 自定义异常体系

实际使用中很多公司都会自定义自己的异常体系进行规范的异常管理,因为一个项目中如果大家
随意抛异常,那么外层的调用者基本就没办法玩了,所以实际中都会定义一套继承的规范体系。
这样大家抛出的都是继承的派生类对象,捕获一个基类就可以了。

// 服务器开发中通常使用的异常继承体系
class Exception
{
public:Exception(const string& errmsg, int id):_errmsg(errmsg), _id(id){}virtual string what() const{return _errmsg;}
protected:string _errmsg;int _id;
};
class SqlException : public Exception
{
public:SqlException(const string& errmsg, int id, const string& sql):Exception(errmsg, id), _sql(sql){}virtual string what() const{string str = "SqlException:";str += _errmsg;str += "->";str += _sql;return str;}
private:const string _sql;
};
class CacheException : public Exception
{
public:CacheException(const string& errmsg, int id):Exception(errmsg, id){}virtual string what() const{string str = "CacheException:";str += _errmsg;return str;}
};
class HttpServerException : public Exception
{
public:HttpServerException(const string& errmsg, int id, const string& type):Exception(errmsg, id), _type(type){}virtual string what() const{string str = "HttpServerException:";str += _type;str += ":";str += _errmsg;return str;}
private:const string _type;
};
void SQLMgr()
{srand(time(0));if (rand() % 7 == 0){throw SqlException("权限不足", 100, "select * from name = '张三'");}//throw "xxxxxx";
}
void CacheMgr()
{srand(time(0));if (rand() % 5 == 0){throw CacheException("权限不足", 100);}else if (rand() % 6 == 0){throw CacheException("数据不存在", 101);}SQLMgr();
}
void HttpServer()
{// ...srand(time(0));if (rand() % 3 == 0){throw HttpServerException("请求资源不存在", 100, "get");}else if (rand() % 4 == 0){throw HttpServerException("权限不足", 101, "post");}CacheMgr();
}
int main()
{while (1){Sleep(1000);try {HttpServer();}catch (const Exception& e) // 这里捕获父类对象就可以{// 多态cout << e.what() << endl;}catch (...){cout << "Unkown Exception" << endl;}}return 0;
}

四 . C++标准库的异常体系的使用

int main()
{try {vector<int> v(10, 5);// 这里如果系统内存不够也会抛异常v.reserve(1000000000);// 这里越界会抛异常v.at(10) = 100;}catch (const exception& e) // 这里捕获父类对象就可以{cout << e.what() << endl;}catch (...){cout << "Unkown Exception" << endl;}return 0;
}


 

五 . 异常的优缺点

优点

1. 异常对象定义好了,相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包
含堆栈调用的信息,这样可以帮助更好的定位程序的bug。
2. 返回错误码的传统方式有个很大的问题就是,在函数调用链中,深层的函数返回了错误,那
么我们得层层返回错误,最外层才能拿到错误,具体看下面的详细解释。

// 1.下面这段伪代码我们可以看到ConnnectSql中出错了,先返回给ServerStart,
ServerStart再返回给main函数,main函数再针对问题处理具体的错误。
// 2.如果是异常体系,不管是ConnnectSql还是ServerStart及调用函数出错,都不用检查,因
为抛出的异常异常会直接跳到main函数中catch捕获的地方,main函数直接处理错误。
int ConnnectSql()
{// 用户名密码错误if (...)return 1;// 权限不足if (...)return 2;
}
int ServerStart() {if (int ret = ConnnectSql() < 0)return ret;int fd = socket()if(fd < 0)return errno;
}
int main()
{if (ServerStart() < 0)...return 0;
}

3. 很多的第三方库都包含异常,比如boost、gtest、gmock等等常用的库,那么我们使用它们
也需要使用异常。
4. 部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码方式处理。比如
T& operator这样的函数,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误。

缺点:

1. 异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳。这会
导致我们跟踪调试时以及分析程序时,比较困难。
2. 异常会有一些性能的开销。当然在现代硬件速度很快的情况下,这个影响基本忽略不计。
3. C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常
安全问题。这个需要使用RAII来处理资源的管理问题。学习成本较高。
4. C++标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常的混乱。
5. 异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获的用户苦不堪言。所以异常
规范有两点:一、抛出异常类型都继承自一个基类。二、函数是否抛异常、抛什么异常,都
使用 func() throw();的方式规范化。


总结:异常总体而言,利大于弊,所以工程中我们还是鼓励使用异常的。另外OO的语言基本都是
用异常处理错误,这也可以看出这是大势所趋。

 

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

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

相关文章

6.7 Windows驱动开发:内核枚举LoadImage映像回调

在笔者之前的文章《内核特征码搜索函数封装》中我们封装实现了特征码定位功能&#xff0c;本章将继续使用该功能&#xff0c;本次我们需要枚举内核LoadImage映像回调&#xff0c;在Win64环境下我们可以设置一个LoadImage映像加载通告回调&#xff0c;当有新驱动或者DLL被加载时…

Android监听用户的截屏、投屏、录屏行为

Android监听用户的截屏、投屏、录屏行为 一.截屏 方案一&#xff1a;使用系统广播监听截屏操作 ​ 从Android Q&#xff08;10.0&#xff09;开始&#xff0c;Intent.ACTION_SCREEN_CAPTURED_CHANGED字段不再被支持。这是因为Google在安卓10 中引入了一个新的隐私限制&#…

zookeeper实操课程Acl 访问权限控制,命令行测试

本系列是zookeeper相关的实操课程&#xff0c;课程测试环环相扣&#xff0c;请按照顺序阅读测试来学习zookeeper。阅读本文之前&#xff0c;请先阅读----​​​​​​zookeeper 单机伪集群搭建简单记录&#xff08;实操课程系列&#xff09;。 阅读本文之前&#xff0c;请先阅读…

Oauth2.0 学习

OAuth 2.0 服务器端通常通过验证每次请求中的访问令牌&#xff08;access token&#xff09;的方式来确保其合法性和有效性。以下是一些通常采用的验证方法&#xff1a; Token Validation Endpoint: OAuth 2.0 规范允许实现一个专门的令牌验证端点&#xff0c;称为 Token Valid…

ipvlan介绍

最近使用docker&#xff0c;涉及到需要跨多台物理机部署系统&#xff0c;查了好多资料&#xff0c;最后查到了ipvlan。那什么是vlan&#xff0c;什么又是ipvlan。 交换机层面的vlan&#xff0c;是按802.1Q规范&#xff0c;在链路层中加了4字节的标识vlan的数据&#xff0c;交换…

YUVRGB

一、直观感受 根据上面的图片&#xff0c;不难看出&#xff1a; RGB的每个分量&#xff0c;是对当前颜色的一个亮度值Y分量对呈现出清晰的图像有着很大的贡献Cb、Cr分量的内容不太容易识别清楚YUV将亮度信息&#xff08;Y&#xff09;与色度信息&#xff08;UV&#xff09;分离…

深入理解原码、反码、补码(结合C语言)

一、引出问题 在学习C语言单目操作符中~按位取反的过程中&#xff0c;对这样一段代码的结果产生了疑惑&#xff1a; #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h>int main() {int a 0;int b ~a;//按位取反printf("%d\n", b);return 0; }输出结果…

TypeScript 变量声明详细教程

文章目录 前言var 声明作用域规则变量获取怪异之处let 声明块作用域重定义及屏蔽块级作用域变量的获取const 声明let vs. const解构数组对象解构属性重命名后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;vue.js &#x1f431;‍&#x…

【MySQL】视图 + 用户管理

视图 前言正式开始视图用户管理user表创建新用户修改用户密码权限管理给用户赋权剥夺权限 前言 本篇所讲的视图和我上一篇事务中所讲的读视图不是一个东西&#xff0c;二者没有任何关系&#xff0c;如果看过我前一篇博客的同学不要搞混了。 其实视图和用户管理本来是想着分开…

华为OD机试真题-虚拟游戏理财-2023年OD统一考试(C卷)

题目描述: 在一款虚拟游戏中生活,你必须进行投资以增强在虚拟游戏中的资产以免被淘汰出局。现有一家Bank,它提供有若干理财产品m,风险及投资回报不同,你有N(元)进行投资,能接受的总风险值为X。 你要在可接受范围内选择最优的投资方式获得最大回报。 说明: 在虚拟游戏中…

CAPL通过在函数内改变全局变量的值

CAPL通过&在函数内改变全局变量的值 先定义一个全局变量。 variables {int tiancihaoche; }再定义一个函数如下: void change_1(int test) {test=555; }测试下: on key 2 {

大数据Doris(三十二):Doris高级功能

文章目录 Doris高级功能 一、​​​​​​​表结构变更

VMware Workstation Pro 17及 Windows 11 虚拟机的安装与激活

六点钟&#xff1a; 吃晚饭吗 不吃&#xff0c;胖胖 十点钟&#xff1a; 阿昊要吃夜宵对不对 ——CSDN&#xff0c;记录牛马生活 本文是在学习 Linux 期间&#xff0c;使用 VMware 时顺带学习 Windows 11 虚拟机的安装与激活 VMware Workstation Pro 17及 Windows 11 虚拟机…

Java Throwable

如图展示了 Java 整个异常体系的关系。 Throwable 的 Java 异常体系的基类, 他的直接子类有 Error 和 Exception 2 个。 1 Error Error 表示的是由于系统错误, Java 虚拟机抛出的异常, 例如 Java 虚拟机崩溃, 内存不够等, 这种情况仅凭程序自身是无法处理的, 在程序中也不会…

JTag 刷写TC397 的Flash

本文实现一个Trace32脚本的示例&#xff0c;用于连接JTAG端口并刷写基于TC397芯片的Flash&#xff1a; /SILENT /NOQUIET /OPENDEBUGGER /VERSION /IFCONNECTION JTAG /CPU TC397 ; 根据具体芯片的型号选择正确的CPU类型 /CONNECT /PROTOCOL JTAG /FREQUENCY 10000000 …

004、简单页面-基础组件

之——基础组件 目录 之——基础组件 杂谈 正文 1.Image 1.0 数据源 1.1 缩放 1.2 大小 1.3 网络图片 2.Text 2.0 数据源 2.1 大小 2.2 粗细 2.3 颜色 2.5 样式字体 2.6 基础示例 2.7 对齐 2.8 省略 2.9 划线 3.TextInput 3.1 输入类型 3.2 提示文…

量子测量-技术点杂录

目录: 高质量文章导航-持续更新中_GZVIMMY的博客-CSDN博客 前置:量子测量设备 电子显微镜:电子显微镜可以在非常高分辨率下观察生物组织、细胞和分子结构。通过调整电子束的强度和聚焦来观察细胞内部的微小结构。但是,电子显微镜需要对样品进行切片处理,而且在真空中进行…

【Android知识笔记】兼容适配专题

屏幕适配 常规适配手段 使用像素密度无关的尺寸单位 避免写死控件,尽量多使用wrap_content、match_parent、weight控件距离使用dp 字体大小使用sp不要用写死的px值布局方面 使用相对布局,禁用绝对布局使用约束布局ConstraintLayout使用百分比布局使用布局限定符 使用尺寸限…

HbuilderX 项目打包文件过大问题优化

文章目录 HbuilderX 项目打包文件过大问题优化主要操作收效甚微&#xff0c;但又有那么点用的方法使用 gulp 压缩&#xff08;最后一步&#xff09;使用与配置 网上找的 gulp 优化压缩配置还未尝试可能有用的方法 尝试过程中看到的一些优质文章 HbuilderX 项目打包文件过大问题…

Shell循环:for(三)

示例&#xff1a;使用for实现批量主机root密码的修改 一、前提 已完成密钥登录配置&#xff08;ssh-keygen&#xff09;定义主机地址列表并了解远程修改密码的方法 [rootlocalhost ~]# ssh-keygen #设置免密登录[rootlocalhost ~]# ssh-copy-id 192.168.151.151 二、演示…