C++异常处理机制(超级详细)
目录
4.1throw基本类型异常(int 、float、char.....)
0.异常处理机制简介
异常无处不在,程序随时可能误入歧途!C++ 提出了新的异常处理机制!
异常是一种程序控制机制,与函数机制互补。
函数是一种以栈结构展开的上下函数衔接的程序控制系统,异常是另一种控制结构,它可以在出现“意外”时中断当前函数,并以某种机制(类型匹配)回馈给隔代的调用者相关的信息。
异常无处不在,程序随时可能误入歧途!C++ 提出了新的异常处理机制!
异常是一种程序控制机制,与函数机制互补。
函数是一种以栈结构展开的上下函数衔接的程序控制系统,异常是另一种控制结构,它可以在出现“意外”时中断当前函数,并以某种机制(类型匹配)回馈给隔代的调用者相关的信息。
图像示例:
当C函数发生错误时,不需要将错误一级级 返回给main,利用异常,直接返回给main:
1.传统错误处理机制(通过函数返回值)
需求: 实现文件的二进制拷贝(当希望文件能够原封不动的拷贝时,要用二进制拷贝)
将要拷贝文件:
代码实现:
- // demo 15-14
- #include <stdio.h>
- #include <stdlib.h>
-
- #define BUFSIZE 1024
-
- //实现文件的二进制拷贝
- int copyfile(const char* dest, const char* src) {
- FILE* fp1 = NULL, * fp2 = NULL;
-
- //rb 只读方式打开一个二进制文件,只允许读取数据
- fopen_s(&fp1, src, "rb");
-
- if (fp1 == NULL) {
- return -1;
- }
-
- //wb 以只写的方式打开或新建一个二进制文件,只允许写数据。
- fopen_s(&fp2, dest, "wb");
- if (fp2 == NULL) {
- return -2;
- }
-
- char buffer[BUFSIZE];
- int readlen, writelen;
-
- //如果读到数据,则大于0
- while ((readlen = fread(buffer, 1, BUFSIZE, fp1)) > 0) {
- writelen = fwrite(buffer, 1, readlen, fp2);
- if (readlen != writelen) {
- return -3;
- }
- }
-
- fclose(fp1);
- fclose(fp2);
- return 0;
- }
-
- void main() {
- int ret = 0;
- //temp.txt为要拷贝的文件,CopyTemp.txt为拷贝文件
- ret = copyfile("CopyTemp.txt","temp.txt");
-
- if (ret != 0) {
- switch (ret) {
- case -1:
- printf("打开源文件失败!
");
- break;
- case -2:
- printf("打开目标文件失败!
");
- break;
- case -3:
- printf("拷贝文件时失败!
");
- break;
- default:
- printf("出现未知的情况!
");
- break;
- }
- }
- system("pause");
- }
运行截图:
拷贝文档截图:
当temp.txt文档不存在时,运行截图:
传统错误返回需要一层层返回错误,而异常处理可以直接到达能够处理异常的地方。
2.异常处理机制语法
- 异常发生第一现场,抛出异常
- void function( ){
- //... ...
- throw 表达式;
- //... ...
- }
- 在需要关注异常的地方,捕捉异常
- try{
- //程序
- function(); 把function至于try中
- //程序
- }catch(异常类型声明){ 比如只写一个int
- //... 异常处理代码 ...
- }catch(异常类型 形参){ 形参将会取得抛出的值
- //... 异常处理代码 ...
- }catch(...){ 抛出的其它异常类型,可以接收任意类型
- //
- }
- 如果没有catch(...),并且没有catch子句与抛出的异常类型匹配,程序会直接中断报错。
- 异常发生第一现场,抛出异常
- void function( ){
- //... ...
- throw 表达式;
- //... ...
- }
- 在需要关注异常的地方,捕捉异常
- try{
- //程序
- function(); 把function至于try中
- //程序
- }catch(异常类型声明){ 比如只写一个int
- //... 异常处理代码 ...
- }catch(异常类型 形参){ 形参将会取得抛出的值
- //... 异常处理代码 ...
- }catch(...){ 抛出的其它异常类型,可以接收任意类型
- //
- }
- 如果没有catch(...),并且没有catch子句与抛出的异常类型匹配,程序会直接中断报错。
修改“文件的二进制拷贝”代码:
- // demo 15-16
- #include <stdio.h>
- #include <stdlib.h>
- #include <string>
-
- using namespace std;
-
- #define BUFSIZE 1024
-
- //实现文件的二进制拷贝
- int copyfile2(const char* dest, const char* src) {
- FILE* fp1 = NULL, * fp2 = NULL;
-
- //通过throw操作创建一个异常对象并抛掷
- //rb 只读方式打开一个二进制文件,只允许读取数据
- fopen_s(&fp1, src, "rb");
-
- if (fp1 == NULL) {
- throw new string("文件不存在");
- }
-
- //wb 以只写的方式打开或新建一个二进制文件,只允许写数据。
- fopen_s(&fp2, dest, "wb");
- if (fp2 == NULL) {
- throw 0.01f;;
- }
-
- char buffer[BUFSIZE];
- int readlen, writelen;
-
- //如果读到数据,则大于0
- while ((readlen = fread(buffer, 1, BUFSIZE, fp1)) > 0) {
- writelen = fwrite(buffer, 1, readlen, fp2);
- if (readlen != writelen) {
- throw - 3;
- }
- }
-
- fclose(fp1);
- fclose(fp2);
- return 0;
- }
-
- //这种写法是将copyfile2的抛出异常给copyfile1,copyfile1再给main
- int copyfile1(const char* dest, const char* src) {
- try {
- copyfile2(dest, src);
- }
- catch (float e) {
- //throw ;
- printf("copyfile1 - catch ...
");
-
- //提示:处理不了的异常,我们可以在catch的最后一个分支,使用throw语法,继续向调用者throw。就比如再抛给main~
- throw; //throw后面可以写接收到的e,也可以不写,不写的话会给出“未使用局部变量”的警告
- }
-
- return 0;
- }
-
- void main() {
- int ret = 0;
-
- //在需要捕捉异常的地方,将可能抛出异常的程序段嵌在try块之中
- //按正常的程序顺序执行到达try语句,然后执行try块{}内的保护段
- //如果在保护段执行期间没有引起异常,那么跟在try块后的catch子句就不执行,程序从try块后跟随
- //的最后一个catch子句后面的语句继续执行下去
- try {//保护段
- printf("开始执行 copyfile1...
");
- ret = copyfile1("CopyTemp.txt", "temp.txt");
- printf("执行 copyfile1 完毕
");
-
- //catch子句按其在try块后出现的顺序被检查,匹配的catch子句将捕获并按catch子句中的代码
- //处理异常(或继续抛掷异常)
- }
- catch (int error) {
- printf("出现异常啦!%%d
", error);
- }
- catch (string* error) {
- printf("捕捉到字符串异常:%%s
", error->c_str());
- delete error;
- }
- catch (float error) {
- printf("出现异常啦!%%f
", error);
- }
- catch (...) {
- printf("catch ...
");
- }
-
-
- //如果没有找到匹配,则缺省功能是调用abort终止程序。
-
- system("pause");
- }
注意:
1.throw后面可跟任何表达式,除了整数外,指针、字符常量等也可以,如:throw "文档打开失败"。
2.通过throw操作创建一个异常对象并抛掷
3.在需要捕捉异常的地方,将可能抛出异常的程序段嵌在try块之中
4.按正常的程序顺序执行到达try语句,然后执行try块{}内的保护段
5.如果在保护段执行期间没有引起异常,那么跟在try块后的catch子句就不执行,程序从try块后跟随的最后一个catch子句后面的语句继续执行下去
6.catch子句按其在try块后出现的顺序被检查,匹配的catch子句将捕获并按catch子句中的代码处理异常(或继续抛掷异常)
7.如果没有找到匹配,则缺省功能是调用abort终止程序。
运行结果:
当temp.txt文档不存在,运行结果:
3.异常接口声明
可以在函数声明中列出可能抛出的所有异常类型,加强程序的可读性。
如:
int copyfile2(char *dest, char *src) throw (float, string *, int) { }
这样,在阅读代码时便可知这个函数会抛出float、string*和int类型异常,增加可读性
注意:
1.对于异常接口的声明,在函数声明中列出可能抛出的所有异常类型
2.如果没有包含异常接口声明,此函数可以抛出任何类型的异常
3.如果函数声明中有列出可能抛出的所有异常类型,那么抛出其它类型的异常讲可能导致程序终止(对于有些编译器可以抛出其它未在声明列表中的异常类型,如老式VC++)
4.如果一个函数不想抛出任何异常,可以使用 throw () 声明(有些编译器会警告throw(),如老式VC++)
4.异常类型和声明周期
4.1throw基本类型异常(int 、float、char.....)
- // demo 15-17
- #include <stdio.h>
- #include <stdlib.h>
- #include <string>
-
- using namespace std;
-
- #define BUFSIZE 1024
-
-
- //实现文件的二进制拷贝
-
- //第一种情况,throw 普通类型,和函数返回传值是一样的
- int copyfile2(const char *dest, const char *src){
- FILE *fp1 = NULL, *fp2 = NULL;
-
- //rb 只读方式打开一个二进制文件,只允许读取数据
- fopen_s(&fp1, src, "rb");
-
- if(fp1 == NULL){
- //int ret = -1;
- char ret = 'a';
- throw ret;
- }
-
- //wb 以只写的方式打开或新建一个二进制文件,只允许写数据。
- fopen_s(&fp2, dest, "wb");
- if(fp2 == NULL){
- throw -2;
- }
-
- char buffer[BUFSIZE];
- int readlen, writelen;
-
- //如果读到数据,则大于0
- while( (readlen = fread(buffer, 1, BUFSIZE, fp1)) > 0 ){
- writelen = fwrite(buffer, 1, readlen, fp2);
- if(readlen != writelen){
- throw -3 ;
- }
- }
-
- fclose(fp1);
- fclose(fp2);
- return 0;
- }
-
- int copyfile1(const char *dest, const char *src){
- return copyfile2(dest, src);
- }
-
- void main(){
- int ret = 0;
-
-
- try{//保护段
- //printf("开始执行 copyfile1...
");
- ret = copyfile1("CopyTemp.txt", "temp.txt");
- //printf("执行 copyfile1 完毕
");
-
- }catch(int error){
- printf("出现异常啦!%%d
", error); //error周期只在此语句中
- }catch(char error){
- printf("出现异常啦!%%c
", error); //error周期只在此语句中
- }
-
- system("pause");
- }
可以在函数声明中列出可能抛出的所有异常类型,加强程序的可读性。
如:
int copyfile2(char *dest, char *src) throw (float, string *, int) { }
这样,在阅读代码时便可知这个函数会抛出float、string*和int类型异常,增加可读性
注意:
1.对于异常接口的声明,在函数声明中列出可能抛出的所有异常类型
2.如果没有包含异常接口声明,此函数可以抛出任何类型的异常
3.如果函数声明中有列出可能抛出的所有异常类型,那么抛出其它类型的异常讲可能导致程序终止(对于有些编译器可以抛出其它未在声明列表中的异常类型,如老式VC++)
4.如果一个函数不想抛出任何异常,可以使用 throw () 声明(有些编译器会警告throw(),如老式VC++)
4.1throw基本类型异常(int 、float、char.....)
- // demo 15-17
- #include <stdio.h>
- #include <stdlib.h>
- #include <string>
-
- using namespace std;
-
- #define BUFSIZE 1024
-
-
- //实现文件的二进制拷贝
-
- //第一种情况,throw 普通类型,和函数返回传值是一样的
- int copyfile2(const char *dest, const char *src){
- FILE *fp1 = NULL, *fp2 = NULL;
-
- //rb 只读方式打开一个二进制文件,只允许读取数据
- fopen_s(&fp1, src, "rb");
-
- if(fp1 == NULL){
- //int ret = -1;
- char ret = 'a';
- throw ret;
- }
-
- //wb 以只写的方式打开或新建一个二进制文件,只允许写数据。
- fopen_s(&fp2, dest, "wb");
- if(fp2 == NULL){
- throw -2;
- }
-
- char buffer[BUFSIZE];
- int readlen, writelen;
-
- //如果读到数据,则大于0
- while( (readlen = fread(buffer, 1, BUFSIZE, fp1)) > 0 ){
- writelen = fwrite(buffer, 1, readlen, fp2);
- if(readlen != writelen){
- throw -3 ;
- }
- }
-
- fclose(fp1);
- fclose(fp2);
- return 0;
- }
-
- int copyfile1(const char *dest, const char *src){
- return copyfile2(dest, src);
- }
-
- void main(){
- int ret = 0;
-
-
- try{//保护段
- //printf("开始执行 copyfile1...
");
- ret = copyfile1("CopyTemp.txt", "temp.txt");
- //printf("执行 copyfile1 完毕
");
-
- }catch(int error){
- printf("出现异常啦!%%d
", error); //error周期只在此语句中
- }catch(char error){
- printf("出现异常啦!%%c
", error); //error周期只在此语句中
- }
-
- system("pause");
- }
对于基本类型异常,使用对应的类型 catch捕捉即可~。
4.2throw字符串类型异常
- // demo 15-18
- #include <stdio.h>
- #include <stdlib.h>
- #include <string>
-
- using namespace std;
-
- #define BUFSIZE 1024
-
-
- int copyfile3(const char* dest, const char* src) {
- FILE* fp1 = NULL, * fp2 = NULL;
-
- //rb 只读方式打开一个二进制文件,只允许读取数据
- fopen_s(&fp1, src, "rb");
-
- if (fp1 == NULL) {
- const char* error = "大佬,你的源文件打开有问题";
- printf("throw 前,error 的地址:%%p
", error);
- //第二种情况,throw 字符串类型,实际抛出的指针,而且,修饰指针的const 也要严格进行类型匹配
- throw error; //常量字符串类型,返回的是个地址
- }
-
- //wb 以只写的方式打开或新建一个二进制文件,只允许写数据。
- fopen_s(&fp2, dest, "wb");
- if (fp2 == NULL) {
- throw - 2;
- }
-
- char buffer[BUFSIZE];
- int readlen, writelen;
-
- //如果读到数据,则大于0
- while ((readlen = fread(buffer, 1, BUFSIZE, fp1)) > 0) {
- writelen = fwrite(buffer, 1, readlen, fp2);
- if (readlen != writelen) {
- throw - 3;
- }
- }
-
- fclose(fp1);
- fclose(fp2);
- return 0;
- }
-
- int copyfile1(const char* dest, const char* src) {
- return copyfile3(dest, src);
- }
-
- void main() {
- int ret = 0;
-
- try {//保护段
- //printf("开始执行 copyfile1...
");
- ret = copyfile1("CopyTemp.txt", "temp.txt");
- //printf("执行 copyfile1 完毕
");
-
- }
- catch (int error) {
- printf("出现异常啦!%%d
", error);
- }
- catch (char error) {
- printf("出现异常啦!%%c
", error);
- }
- catch (string error) {
- printf("出现异常啦!%%s
", error.c_str());
- }
- //严格类型匹配,这里也需要加const
- catch (const char* error) { //接收常量字符串类型异常地址
- printf("出现异常啦(char *)!%%s(地址:%%p)
", error, error);
- }
- catch (...) {
- printf("没捉到具体的异常类型
");
- }
-
- system("pause");
- }
当抛出的异常类型为地址(指针)时候,catch捕捉类型也要为指针。并且,当抛出异常类型有const修饰时,捕捉类型也要有cosnt修饰。即严格类型匹配。
运行结果:
4.3throw类对象类型异常
当抛出的异常类型为一个类对象时:
- #include <stdio.h>
- #include <stdlib.h>
- #include <string>
-
-
-
- class ErrorException {
- public:
- ErrorException() {
- id = 0;
- printf("ErrorException 构造!
");
- }
-
- ~ErrorException() {
- printf("ErrorException ~析构!(id: %%d)
", id);
- }
-
- ErrorException(const ErrorException& e) {
- id = 1;
- printf("ErrorException 拷贝构造函数!
");
- }
-
- int id;
- };
-
-
下面对throw语句进行分类研究:
4.3.1第一种:抛出匿名对象,使用形参接收
-
- throw ErrorException(); //抛出匿名对象
-
- catch (ErrorException error) {
- printf("出现异常啦!捕捉到 ErrorException 类型 id: %%d
", error.id);
- }
运行结果:
4.3.2第二种: 抛出局部变量,使用形参接收
- ErrorException error1;
- throw error1;
-
- catch (ErrorException error) {
- error.id=2; //增加一句error.id=2,便于惯出
- printf("出现异常啦!捕捉到 ErrorException 类型 id: %%d
", error.id);
- }
运行结果:
由以上两种结果可得,不管是抛出匿名对象,还是抛出局部变量,程序都会生成一个匿名对象,并且,当catch是普通参数时,程序还要再次调用拷贝函数。
4.3.3第三种:抛出匿名对象,使用引用接收(代码优化 )
- throw ErrorException(); //直接抛出匿名对象
-
- catch (ErrorException& error) { 用引用方式来接
- //error.id = 2;
- printf("出现异常啦!捕捉到 ErrorException &类型 id: %%d
", error.id);
- }
运行结果:
当然也可以 动态分配内存,直接抛出指针,当然也要用指针接收。(严格类型匹配)
- throw new ErrorException();
-
- catch (ErrorException* error) {
- printf("出现异常啦!捕捉到 ErrorException *类型 id: %%d
", error->id);
- delete error;
- }
运行结果:
注意: 引用和普通的形参传值不能共存!
因为引用和普通形参都可以catch捕捉,编译器无法识别被哪个捕捉 。
5.异常和继承
异常也是类,我们可以创建自己的异常类,在异常中可以使用(虚函数,派生,引用传递和数据成员等)。
案例:设计一个数组类容器 Vector,重载[]操作,数组初始化时,对数组的个数进行有效检查
1.index<0 抛出异常errNegativeException
2.index = 0 抛出异常 errZeroException
3.index>1000抛出异常errTooBigException
4.index<10 抛出异常errTooSmallException
5.errSizeException类是以上类的父类,实现有参数构造、并定义virtual void printError()
异常也是类,我们可以创建自己的异常类,在异常中可以使用(虚函数,派生,引用传递和数据成员等)。
案例:设计一个数组类容器 Vector,重载[]操作,数组初始化时,对数组的个数进行有效检查
1.index<0 抛出异常errNegativeException
2.index = 0 抛出异常 errZeroException
3.index>1000抛出异常errTooBigException
4.index<10 抛出异常errTooSmallException
5.errSizeException类是以上类的父类,实现有参数构造、并定义virtual void printError()
Vector容器实现:
- #include<iostream>
- using namespace std;
-
- class Vector{
- public:
- Vector(int length = 128);
- int getLength();
- int& operator[](int index);
- ~Vector();
- private:
- int*m_base;
- int m_length;
- };
-
- Vector::Vector(int length) {
- m_length = length;
- m_base = new int[length];
- }
-
- int Vector::getLength() {
- return m_length;
- }
-
- //引用方式返回可以作为左值修改(即存储,Vector va(5); va[1]=0)
- int& Vector::operator[](int index) {
- return m_base[index];
- }
-
- Vector::~Vector() {
- if (m_base) {
- delete[] m_base;
- m_length = 0;
- }
- }
异常类实现:
- class errNegativeException {
-
- };
- class errZeroException {
-
- };
- class errTooBigException {
-
- };
- class errTooSmallException {
-
- };
修改Vector的构造函数:
- Vector::Vector(int length) {
- if (length < 0) {
- throw errNegativeException();
- }
- else if (length == 0) {
- throw errZeroException();
- }
- else if (length > 1000) {
- throw errTooBigException();
- }
- else if (length <= 10) {
- throw errTooSmallException();
- }
- m_length = length;
- m_base = new int[length];
- }
实现主函数接口:
- int main() {
- try {
- Vector va(10);
- for (int i = 0; i < va.getLength(); i++) {
- va[i] = i + 3;
- }
- }
- catch (errNegativeException& erro) {
- cout << "errNegativeException" << endl;
- }
- catch (errZeroException& erro) {
- cout << "errZeroException" << endl;
- }
- catch (errTooBigException& erro) {
- cout << "errTooBigException" << endl;
- }
- catch (errTooSmallException& erro) {
- cout << "errTooSmallException" << endl;
- }
-
- system("pause");
- return 0;
- }
运行结果:
这种抛出异常方法跟直接return一个结果没啥两样,现在通过以前学习的虚函数进行代码优化:
异常类代码优化:
- class errSizeException {
- public:
- errSizeException(int size) {
- m_size = size;
- }
- virtual void printErroe() {
- cout << "size:" << m_size << endl;
- }
- protected:
- int m_size;
- };
- //子类errNegativeException
- class errNegativeException :public errSizeException {
- public:
- errNegativeException(int size) :errSizeException(size){}
- virtual void printErroe() {
- cout << "errNegativeException size:" << m_size << endl;
- }
- };
- //子类errZeroException
- class errZeroException :public errSizeException {
- public:
- errZeroException(int size) :errSizeException(size) {}
- virtual void printErroe() {
- cout << "errZeroException size:" << m_size << endl;
- }
- };
- //子类errTooBigException
- class errTooBigException :public errSizeException {
- public:
- errTooBigException(int size) :errSizeException(size) {}
- virtual void printErroe() {
- cout << "errTooBigException size:" << m_size << endl;
- }
- };
- //子类errTooSmallException
- class errTooSmallException :public errSizeException {
- public:
- errTooSmallException(int size) :errSizeException(size) {}
- virtual void printErroe() {
- cout << "errTooSmallException size:" << m_size << endl;
- }
- };
-
父类是 errSizeException,子类继承父类。通过虚函数,实现子类代替父类。
主函数接口:
-
- int main() {
- try {
- Vector va(10);
- for (int i = 0; i < va.getLength(); i++) {
- va[i] = i + 3;
- }
- }
- catch (errSizeException& erro) {
- erro.printErroe();
- }
-
-
- system("pause");
- return 0;
- }
运行结果:
6. 额外补充——标准库里的异常类
无需硬背,混个眼熟即可~
代码示例:
- #include <iostream>
- #include <exception>
- #include <stdexcept>
-
- using namespace std;
-
- class Student{
- public:
- Student(int age){
- if(age > 249){
- throw out_of_range("年龄太大,你是外星人嘛?");
- }
- m_age = age;
- m_space = new int[1024*1024*100];
- }
-
- private :
- int m_age;
- int *m_space;
- };
-
-
- void main(){
-
- try{
- for(int i=1; i<1024; i++){
- Student * xiao6lang = new Student(18);
- }
- }catch(out_of_range &e){
- cout<<"捕捉到一只异常:"<<e.what()<<endl;
- }catch(bad_alloc &e){
- cout<<"捕捉到动态内存分配的异常:"<<e.what()<<endl;
- }
-
- system("pause");
- }
运行结果: