C++面向对象——异常处理

异常处理

异常是指程序在运行过程中因环境条件出现意外或用户操作不当而导致程序不能正常运行。
其中有些异常情况是可以预料但不可避免的,例如在申请堆内存空间时,因内存空间不足使分配空间操作失败;
访问磁盘文件时因文件不存在使打开文件操作失败;执行打印操作时因打印机未打开使打印出错;
执行除法运算时因除数为0而出错等。对于这些可能发生的意外情况,在编写程序时应充分考虑并给予适当的处理,尽可能做到排除错误,使程序继续运行或者给出适当的提示信息后中断程序的运行。

异常Exception是一种不常见或者不可预见的情况,经常导致中断正常的程序流。
常见的可能产生异常的操作:数值越界,文件操作,内存分配,Windows资源,实时生成的对象与窗体,硬件和操作系统产生的冲突等。

异常处理的方法

异常处理的方法有多种,可以在可能发生错误的程序段中直接加上错误处理的代码,这样做的好处是阅读程序时能够直接看到对错误进行处理的情况,不足之处是使程序变得繁琐、可读性差。C++的异常处理机制允许发生异常的代码与处理异常的代码不在同一函数中。

C++异常处理机制中实现异常处理的方法是:

  1. 监测异常:将可能发生异常的代码包含在try语句块中。
  2. 抛掷异常:在try代码块中或其调用函数的代码中可能发生异常的地方进行检测,若检测到异常,则用throw语句抛出异常。
  3. 捕获异常:在try代码块后给出catch代码块,在其中给出处理异常的语句,用于捕获和处理异常。

如果一个异常没有被调用链中的任何函数捕捉到,那么在主函数捕捉该异常失败之后,按照默认,该程序就会自动调用abort()函数来终止

throw语句的一般形式如下:
throw <表达式>;

try catcht 语句的一般形式如下:
try{
    //try语句块
} catch(类型1 [变量1]){
    //针对类型1的异常处理语句块
}catch(类型2 [变量2]) {
    //针对类型2的异常处理语句块
}...catch(类型N [变量N]){
    //针对类型N的异常处理语句块
}catch(...){
    //处理任何类型异常,放在最后
}
#include <iostream>
using namespace std;
int Div(int x,int y) {
    if(y==0) throw y; //如果除数为0,throw语句抛掷整型异常
    return x/y;
}
int main() {
    try {  //由于除法运算可能出现除0异常,因此放在try语句块中监测
        cout<<"7/3="<<Div(7,3)<<endl;
        cout<<"7/0="<<Div(7,0)<<endl;
        cout<<"7/1="<<Div(7,1)<<endl;  //该语句不被执行
    } catch(int i) {  //catch语句块捕获整型异常并进行处理
        cout<<"exception of dividing zero\n";
    }
    cout<<"That is ok.\n";
    return 0;
}

(1)主函数中调用了计算两个整数相除结果的函数Div,当除数为0时Div函数的运行将发生除0异常,所以在主函数中将调用Div函数的三条语句包含在try语句块中进行监测。

(2)当执行到try块的第二条语句时,调用了函数Div(7,0),由于除数为0,在Div函数中抛掷了一个异常throw y,其中y为整数,该异常为整型异常。这时将退出div函数,从这一点来看throw语句的作用类似于return语句。

(3)与return语句不同的是退出Div函数后,不是返回调用该函数语句的下一条语句:cout<<“7/1=”<<Div(7,1)<<endl;

继续执行,而是在try语句块后寻找能捕获整型异常的catch语句块。

try语句块后只有一个catch语句块且该语句块带一个整型参数i,该catch语句块可以捕获整型异常。

(4)catch语句块的形参i被初始化为throw语句中y的值,因i在catch块中未用到,故可将i省略,但参数类型不能省略,然后进行退出Div函数的退栈处理并释放相关的资源。

(5)执行完catch语句块中处理异常的语句后,则执行catch块后的第一条语句:

cout<<“That is ok.\n”;

#include<iostream>
#include<cmath>
#include<cstdio>
using namespace std;
int Div(int x,int y){
    if(y==0) throw y;
    return x/y;
}
double Sqrt(double y){
    if(y<0) throw y;
    return sqrt(y); 
}
int main(){
    double a,b; cin>>a>>b;
    try{
        cout<<"div("<<a<<"/"<<b<<")="<<Div(a,b)<<endl;
        cout<<"sqrt("<<b<<")="<<Sqrt(b)<<endl;
    }catch(int){
        cout<<"exception of dividing zero\n";
    } catch(...){ //处理任何类型异常 
        cout<<"caught exception\n";
    }
    cout<<"That is ok.\n";
    return 0;
}

异常处理的规则

编写异常处理程序时应注意遵守以下规则:

(1)应将可能发生异常的程序代码段包括对某个函数的调用包含在try块中,以便对其进行监控,C++异常处理机制只对受监控代码段发生的异常进行处理。

(2)在受监控代码段中所有可能发生异常之处加上检测语句,一旦检测到异常情况,立即用throw语句抛掷这个异常。throw语句抛掷的异常的类型由throw后所跟的表达式值的类型所决定,如代码段中有多处要抛掷异常,应该用不同类型的操作数加以区别,类型相同而仅仅值不相同的操作数不能区别不同的异常。

(3)throw语句抛掷的异常由try语句块后的catch语句块捕获并处理,catch语句块可以有多个,通常每个catch语句块捕获一种类型的异常,捕获异常的类型由catch语句块参数类型所决定,catch块可以捕获其参数类型及其派生类型的异常。当catch语句块中未用到参数时,可只给出参数类型声明而省略参数名。

(4)当catch语句块的参数类型声明是一个省略号(…)时,该catch语句块可以捕获和处理任何类型的异常,该catch语句块应放在所有其它catch块的后面。

(5)如果抛掷异常后未找到能够处理该类异常的catch块,则自动调用运行函数terminate,该函数调用abort函数终止程序的运行。

#include<iostream>
using namespace std;
void f(int x) {
    try {
        if(x>0)  throw 2;
        if(x==0) throw 'a';
        if(x<0)  throw 3.14;
    } catch(int n) {
        cout<<"输入的为正数:"<<x<<endl;
    } catch(char m) {
        cout<<"输入的为零:"<<x<<endl;
    } catch(double k) {
        cout<<"输入的为负数:"<<x<<endl;
    } catch(...) {
        cout<<"捕获所有类型的异常!"<<"输入的数字是:"<<x<<endl;
    }
}
int main() {
    f(4); f(0); f(-5);
    return 0;
}
#include<iostream>
using namespace std;
void f() {
    try {
        throw 'a'; //重抛异常
    } catch(char x) {
        cout<<"内层异常处理!"<<endl;
        throw 'b';
    }
}
int main() {
    try {
        f();
    } catch(char x) {
        cout<<"外层异常处理!"<<endl;
    }
    return 0;
}
#include<iostream>
using namespace std;
//演示C++中的异常处理对象的构造和析构
class Demo {
    public:
        int x;
        Demo(int y) {
            x = y;
            cout<<"进入Demo类的构造函数"<<endl;
            if(x<0) throw x;
            else cout<<"调用Demo类的构造函数,构造对象"<<endl;
        }
        ~Demo() {
            cout<<"调用Demo类的析构函数,析构对象"<<endl;
        }
};
void func() {
    cout<<"进入函数func"<<endl;
    Demo d1(4), d2(-8);
    throw 'A';
}
int main() {
    cout<<"主函数开始执行"<<endl;
    try {
        cout<<"调用func函数"<<endl;
        func();
    } catch(int n) {
        cout<<"对象"<<n<<"发生错误"<<endl;
    } catch(char m) {
        cout<<"在函数中抛出异常"<<endl;
    }
    cout<<"主函数执行完毕"<<endl;
    return 0;
}

例:异常处理中类对象的析构

#include <iostream>
using namespace std;
void func();
class Expt {
    public:
        Expt() {}
        ~Expt() {}
        char *showReason()const {
            return "Expt类异常。";
        }
};
class Demo {
    public:
        Demo() {
            cout<<"构造Demo。"<<endl;
        }
        ~Demo() {
            cout<<"析构Demo。"<<endl;
        }
};
void func() {
    Demo d;
    cout<<"在func函数中抛掷Expt类异常。"<<endl;
    throw Expt();           //创建Expt类无名对象以抛掷Expt类异常
}
int main() {
    cout<<"在主函数中。"<<endl;
    try {
        cout<<"在try块中调用func函数。"<<endl;
        func();
    } catch(Expt e) {       //捕获Expt类异常
        cout<<"在catch异常处理程序中。"<<endl;
        cout<<"捕获到Expt类型异常:"<<e.showReason()<<endl;
    } catch(char *str) {
        cout<<"捕获到其它类型的异常:"<<str<<endl;
    }
    cout<<"回到main函数,从这里恢复执行。"<<endl;
    return 0;
}

程序运行结果:

在主函数中。
在try块中调用func函数。
构造Demo。
在func函数中抛掷Expt类异常。
析构Demo。
在catch异常处理程序中。
捕获到Expt类型异常:Expt类异常
回到main函数,从这里恢复执行。

例:构造函数中发生异常的处理

#include<iostream>
using namespace std;
class Birthday {
    public:
        void init(int y,int m,int d) {
            if(y<1||m<1||m>12||d<1||d>31) throw y;
        }
        Birthday(int y,int m, int d):year(y),month(m),day(d) {
            init(y,m,d);
            cout<<"Birthday\n";
        }
        void display() {
            cout<<"Birthday:"<<year<<"/"<<month<<"/"<<day<<endl;
        }
        ~Birthday() {
            cout<<"~Birthday\n";   //构造不成功,析构函数不执行
        }
    private:
        int year, month, day;
};
int main() {
    try {
        Birthday birth(1996,13,0);
        birth.display();
    } catch(int) {
        cout<<"Object Creation Failed!"<<endl;
    }
    return 0;
}

程序运行结果:

Object Creation Failed!

例:有子对象的派生类构造函数中发生异常的处理

#include <iostream>
using namespace std;
class Birthday {
    public:
        Birthday(int y=2000,int m=01,int day=01) {
            cout<<"Construct Birthday."<<endl;
        }
        ~Birthday() {
            cout<<"Destruct Birthday."<<endl;
        }
    private:
        int year, month, day;
};
class Father {
    public:
        Father(int i):age(i) {
            cout<<"Construct Father."<<endl;
        }
        ~Father() {
            cout<<"Destruct Father."<<endl;
        }
    protected:
        int age;
};
class Son: public Father {
    public:
        Son(int age):Father(age) { //包含父类对象和子对象的构造函数
            cout<<"Construct Son."<<endl;
            throw age; //执行子对象类和父类的析构函数,不执行派生类的析构函数
        }
        ~Son() {
            cout<<"Destruct Son."<<endl;
        }
    private:
        Birthday bir;  //子对象
};
int main() {
    try {
        Son(19);
    } catch(int) {
        cout<<"Exception."<<endl;
    }
    return 0;
}

程序运行结果:

Construct Father.
Construct Birthday.
Construct Son.
Destruct Birthday.
Destruct Father.
Exception.
  • 13
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本资源是压缩包形式的, 里面包含 本书,里面是pdf格式的, 带书签目录,本书是完整版的。 资源都是我自己用过的,不骗大家。 本书作者: 肖俊宇 吴为胜; 出版社: 电子工业出版社 内容简介: 《由浅入深学C++:基础、进阶与必做300题(含DVD光盘1张)》是C++语言的入门教程,较为系统地介绍了C++语言的基础内容。本书共分为3篇22章,详细介绍了C++语言的基础知识、面向对象、标准模块、底层开发和综合案例。本书循序渐进地讲述了C++的基础知识、C++程序的组成及其开发过程、C++程序中的数据、表达式和语句、控制程序流程、数组与字符串、指针与引用、使用函数、函数模板、错误和异常处理、宏和预编译、面向对象开发、封装、继承、多态、类模板、文件流、标准模板库STL和编程实践等内容。 《由浅入深学C++:基础、进阶与必做300题(含DVD光盘1张)》涉及面广,从基本知识到高级内容和核心概念,再到综合案例,几乎涉及C++开发的所有重要知识。本书适合所有想全面学习C++开发技术的人员阅读,尤其适合没有编程基础的C++语言初学者作为入门教程,也可作为大、中院校师生和培训班的教材,对于C++语言开发爱好者,本书也有较大的参考价值。 章节目录: 第1篇 C++基础篇 第1章 C++概述 1 1.1 引言 1 1.1.1 C++的历史沿革 1 1.1.2 入门C++ 2 1.1.3 编程思想的转变 3 1.2 C++概述 4 1.2.1 C++的特征 5 1.2.2 C与C++的比较 5 1.2.3 C++的应用领域 6 1.3 C++源程序的组成 6 1.3.1 基本组成元素 7 1.3.2 标识符 8 1.3.3 保留字 8 1.3.4 符号 8 1.4 C++集成开发环境——DEV-C++ 9 1.4.1 选择C++编译器 9 1.4.2 安装DEV-C++ 10 1.4.3 DEV-C++ IDE简介 11 1.5 第一个C++程序——Hello World 11 1.5.1 创建源程序 11 1.5.2 编译运行 13 1.6 小结 14 1.7 习题 14 第2章 变量与数据类型 18 2.1 常量和变量 18 2.1.1 常量 18 2.1.2 变量 21 2.1.3 变量的定义及赋值 22 2.1.4 变量的应用示例 24 2.2 基本数据类型 25 2.2.1 基本数据类型概述 25 2.2.2 整型数据类型 26 2.2.3 浮点型数据类型 27 2.2.4 字符型数据类型 29 2.2.5 布尔型数据类型 30 2.3 变量的作用域 31 2.4 类型转换 32 2.4.1 隐式转换 32 2.4.2 显式转换 33 2.5 小结 34 2.6 习题 34 第3章 表达式与语句 39 3.1 运算符 39 3.1.1 运算符概述 39 3.1.2 算术运算符 40 3.1.3 自增和自减运算符 42 3.1.4 赋值运算符 43 3.1.5 关系运算符 44 3.1.6 逻辑运算符 45 3.1.7 条件运算符 46 3.1.8 逗号运算符 47 3.1.9 位运算符 48 3.1.10 sizeof运算符 49 3.2 运算符的优先级和结合性 50 3.3 表达式 51 3.4 语句 53 3.4.1 空格的作用 53 3.4.2 语句块 54 3.4.3 赋值语句 55 3.4.4 空语句 56 3.5 小结 57 3.6 习题 57 第4章 流程控制结构之顺序结构 63 4.1 程序流程图 63 4.2 表达式语句 64 4.3 格式化输入/输出 65 4.3.1 标准输入流cin 65 4.3.2 标准输出流cout 66 4.3.3 输出流cerr和clog 68 4.4 格式控制函数 69 4.5 格式控制符 71 4.5.1 控制不同进制的输出 72 4.5.2 控制输出宽度 72 4.5.3 控制输出精度 73 4.6 顺序结构综合应用 74 4.7 小结 75 4.8 习题 75
本书作者根据自己学习C++的亲身体会及多年教学经验,用简单的例子和简练的叙述讲解C++编程,别具特色。 全书共分十八章,内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、 动态对象创建、继承和组合、多态和虚函数、模板和包容器类、多重继承、异常处理和运行时类型识别。 本书作为正式教材和自学用书均非常优秀,作为程序设计者的参考用书亦极为合适。 目 录 译者序 前言 第1章 对象的演化 1 1.1 基本概念 1 1.1.1 对象:特性+行为 1 1.1.2 继承:类型关系 1 1.1.3 多态性 2 1.1.4 操作概念:OOP程序像什么 3 1.2 为什么C++会成功 3 1.2.1 较好的C 3 1.2.2 采用渐进的学习方式 4 1.2.3 运行效率 4 1.2.4 系统更容易表达和理解 4 1.2.5 “库”使你事半功倍 4 1.2.6 错误处理 5 1.2.7 大程序设计 5 1.3 方法学介绍 5 1.3.1 复杂性 5 1.3.2 内部原则 6 1.3.3 外部原则 7 1.3.4 对象设计的五个阶段 9 1.3.5 方法承诺什么 10 1.3.6 方法应当提供什么 10 1.4 起草:最小的方法 12 1.4.1 前提 13 1.4.2 高概念 14 1.4.3 论述(treatment) 14 1.4.4 结构化 14 1.4.5 开发 16 1.4.6 重写 17 1.4.7 逻辑 17 1.5 其他方法 17 1.5.1 Booch 18 1.5.2 责任驱动的设计(RDD) 19 1.5.3 对象建模技术(OMT) 19 1.6 为向OOP转变而采取的策略 19 1.6.1 逐步进入OOP 19 1.6.2 管理障碍 20 1.7 小结 21 第2章 数据抽象 22 2.1 声明与定义 22 2.2 一个袖珍C库 23 2.3 放在一起:项目创建工具 29 2.4 什么是非正常 29 2.5 基本对象 30 2.6 什么是对象 34 2.7 抽象数据类型 35 2.8 对象细节 35 2.9 头文件形式 36 2.10 嵌套结构 37 2.11 小结 41 2.12 练习 41 第3章 隐藏实现 42 3.1 设置限制 42 3.2 C++的存取控制 42 3.3 友元 44 3.3.1 嵌套友元 45 3.3.2 它是纯的吗 48 3.4 对象布局 48 3.5 类 48 3.5.1 用存取控制来修改stash 50 3.5.2 用存取控制来修改stack 51 3.6 句柄类(handle classes) 51 3.6.1 可见的实现部分 51 3.6.2 减少重复编译 52 3.7 小结 54 3.8 练习 54 第4章 初始化与清除 55 4.1 用构造函数确保初始化 55 4.2 用析构函数确保清除 56 4.3 清除定义块 58 4.3.1 for循环 59 4.3.2 空间分配 60 4.4 含有构造函数和析构函数的stash 61 4.5 含有构造函数和析构函数的stack 63 4.6 集合初始化 65 4.7 缺省构造函数 67 4.8 小结 68 4.9 练习 68 第5章 函数重载与缺省参数 69 5.1 范围分解 69 5.1.1 用返回值重载 70 5.1.2 安全类型连接 70 5.2 重载的例子 71 5.3 缺省参数 74 5.4 小结 81 5.5 练习 82 第6章 输入输出流介绍 83 6.1 为什么要用输入输出流 83 6.2 解决输入输出流问题 86 6.2.1 预先了解操作符重载 86 6.2.2 插入符与提取符 87 6.2.3 通常用法 88 6.2.4 面向行的输入 90 6.3 文件输入输出流 91 6.4 输入输出流缓冲 93 6.5 在输入输出流中查找 94 6.6 strstreams 96 6.6.1 为用户分配的存储 96 6.6.2 自动存储分配 98 6.7 输出流格式化 100 6.7.1 内部格式化数据 101 6.7.2 例子 102 6.8 格式化操纵算子 106 6.9 建立操纵算子 108 6.10 输入输出流实例 111 6.10.1 代码生成 111 6.10.2 一个简单的数据记录 117 6.11 小结 123 6.12 练习 123 第7章 常量 124 7.1 值替代 124 7.1.1 头文件里的const 124 7.1.2 const的安全性 125 7.1.3 集合 126 7.1.4 与C语言的区别 126 7.2 指针 127 7.2.1 指向const的指针 127 7.2.2 const指针 127 7.2.3 赋值和类型检查 128 7.3 函数参数和返回值 128 7.3.1 传递const值 128 7.3.2 返回const值 129 7.3.3 传递和返回地址 131 7.4 类 133 7.4.1 类里的const和enum 133 7.4.2 编译期间类里的常量 134 7.4.3 const对象和成员函数 136 7.4.4 只读存储能力 139 7.5 可变的(volatile) 140 7.6 小结 141 7.7 练习 141 第8章 内联函数 142 8.1 预处理器的缺陷 142 8.2 内联函数 144 8.2.1 类内部的内联函数 145 8.2.2 存取函数 146 8.3 内联函数和编译器 150 8.3.1 局限性 150 8.3.2 赋值顺序 150 8.3.3 在构造函数和析构函数里隐藏行为 151 8.4 减少混乱 152 8.5 预处理器的特点 153 8.6 改进的错误检查 154 8.7 小结 155 8.8 练习 155 第9章 命名控制 157 9.1 来自C语言中的静态成员 157 9.1.1 函数内部的静态变量 157 9.1.2 控制连接 160 9.1.3 其他的存储类型指定符 161 9.2 名字空间 161 9.2.1 产生一个名字空间 162 9.2.2 使用名字空间 163 9.3 C++中的静态成员 166 9.3.1 定义静态数据成员的存储 166 9.3.2 嵌套类和局部类 168 9.3.3 静态成员函数 169 9.4 静态初始化的依赖因素 171 9.5 转换连接指定 174 9.6 小结 174 9.7 练习 174 第10章 引用和拷贝构造函数 176 10.1 C++中的指针 176 10.2 C++中的引用 176 10.2.1 函数中的引用 177 10.2.2 参数传递准则 178 10.3 拷贝构造函数 179 10.3.1 传值方式传递和返回 179 10.3.2 拷贝构造函数 182 10.3.3 缺省拷贝构造函数 187 10.3.4 拷贝构造函数方法的选择 188 10.4 指向成员的指针
面向对象的编程中,C语言并不直接支持类和抽象的概念。引用中提到,final关键字用来修饰方法,表示该方法不能在子类中被覆盖。而abstract关键字用来修饰抽象方法,表示该方法必须在子类中被实现。然而,在C语言中,没有对应的关键字来实现类和抽象的概念。 相反,C语言通过结构体来模拟类的概念。结构体是一种用户自定义的数据类型,可以包含多个不同类型的数据成员。通过结构体,我们可以将相关的数据和功能组合在一起。然而,C语言中的结构体不支持继承和多态等面向对象的特性。 在C语言中,我们可以使用函数指针来模拟抽象类和接口的概念。函数指针可以指向不同的函数,通过使用函数指针,我们可以实现多态性,即在运行时根据函数指针指向的具体函数来执行不同的操作。 综上所述,C语言并不直接支持面向对象中的类和抽象的概念,但可以使用结构体和函数指针来实现类似的功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [面向对象——类和对象](https://blog.csdn.net/shouyeren_st/article/details/126210622)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [面向对象编程原则(06)——依赖倒转原则](https://blog.csdn.net/lfdfhl/article/details/126673771)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值