更多精彩内容.....
🎉❤️播主の主页✨😘
本文所在专栏:
座右铭:梦想是一盏明灯,照亮我们前行的路,无论风雨多大,我们都要坚持不懈。
异常是一种程序控制机制,与函数机制独立和互补:函数是一种以栈结构展开的上下函数衔接的程序控制系统,异常是另一种控制结构,它依附于栈结构,却可以同时设置多个异常类型作为网捕条件,从而以类型匹配在栈机制中跳跃回馈。
异常设计目的:栈机制是一种高度节律性控制机制,面向对象编程却要求对象之间有方向、有目的的控制传动,从一开始,异常就是冲着改变程序控制结构,以适应面向对象程序更有效地工作这个主题,而不是仅为了进行错误处理。
异常设计出来之后,却发现在错误处理方面获得了最大的好处。处理错误的这个过程就是抛异常。
一、异常处理的基本思想
传统的异常处理机制是C语言使用的通过函数返回值来获取错误信息然后通过if等条件语句处理这些发生的错误。
1)C++的异常处理机制使得异常的引发和异常的处理不必在同一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理。上层调用者可以再适当的位置设计对不同类型异常的处理。
2)异常是专门针对抽象编程中的一系列错误处理的,C++中不能借助函数机制,因为栈结构的本质是先进后出,依次访问,无法进行跳跃,但错误处理的特征却是遇到错误信息就想要转到若干级之上进行重新尝试,如图
3)异常超脱于函数机制,决定了其对函数的跨越式回跳。
4)异常跨越函数
二、异常处理的实现方法
C++中的异常处理是通过try
、catch
和throw
关键字来实现的。它允许程序在运行时处理错误,使得程序的控制流可以在出现异常时更优雅地转移。下面是C++异常处理的基本结构和使用示例:
try:用于包裹可能会抛出异常的代码。
catch:用于捕获异常并处理它。
throw:用于抛出一个异常。
#include <iostream>
#include <stdexcept>
void riskyFunction() {
// 随机抛出一个异常
throw std::runtime_error("发生了一个运行时错误");
}
int main() {
try {
riskyFunction();
} catch (const std::runtime_error& e) {
std::cerr << "捕获到异常: " << e.what() << std::endl;
} catch (...) {
std::cerr << "捕获到未知异常" << std::endl;
}
std::cout << "程序继续运行..." << std::endl;
return 0;
}
riskyFunction
: 这是一个声明的函数,故意抛出一个std::runtime_error
异常。try
块:在这里放置调用可能抛出异常的函数。catch
块:捕获特定类型的异常,并执行相应的处理。在这个示例中,我们捕获std::runtime_error
类型的异常。e.what()
: 这是std::runtime_error
类中的一个方法,返回错误消息。
1) 若有异常则通过throw操作创建一个异常对象并抛掷。
2) 将可能抛出异常的程序段嵌在try块之中。控制通过正常的顺序执行到达try语句,然后执行try块内的保护段。
3) 如果在保护段执行期间没有引起异常,那么跟在try块后的catch子句就不执行。程序从try块后跟随的最后一个catch子句后面的语句继续执行下去。
4) catch子句按其在try块后出现的顺序被检查。匹配的catch子句将捕获并处理异常(或继续抛掷异常)。
5) 如果匹配的处理器未找到,则运行函数terminate将被自动调用,其缺省功能是调用abort终止程序。
6)处理不了的异常,可以在catch的最后一个分支,使用throw语法,向上扔。
三、异常处理的重要概念
异常的类型:可以抛出不同类型的异常,通常使用标准库中的异常类(如std::exception
、std::runtime_error
等)。
捕获多个异常:可以使用多个catch
块捕获不同类型的异常。
未知异常:使用catch (...)
来捕获所有未被捕获的异常类型。
异常安全:设计函数时要考虑异常的影响,确保在发生异常时可以保持程序的状态一致性。
异常传播:如果在一个函数中抛出异常而没有捕获它,则异常会被传播到调用该函数的代码层次。
自定义异常:可以通过继承std::exception
类定义自己的异常类型。
#include <iostream>
#include <exception>
class MyException : public std::exception {
public:
const char* what() const noexcept override {
return "这是一个自定义异常";
}
};
void myFunction() {
throw MyException();
}
int main() {
try {
myFunction();
} catch (const MyException& e) {
std::cerr << "捕获到自定义异常: " << e.what() << std::endl;
}
return 0;
}
这段代码展示了如何定义和使用自定义的异常类型。在实际开发中,使用异常处理机制可以提高程序的健壮性和可读性。
四、异常处理的机制分析
①经典案例:被零整除
int divide(int x, int y )
{
if (y ==0)
{
throw x;
}
return x/y;
}
int main()
{
try
{
cout << "8/2 = " << divide(8, 2) << endl;
cout << "10/0 =" << divide(10, 0) << endl;
}
catch (int e)
{
cout << "e" << " is divided by zero!" << endl;
}
catch(...)
{
cout << "未知异常" << endl;
}
cout << "ok" << endl;
system("pause");
return 0;
}
②异常:函数机制
class A{};
void f(){
if(...) throw A;
}
void g(){
try{
f();
}catch(B){
cout<<“exception B\n”;
}
}
int main(){
g();
return 0;
}
throw A将穿透函数f,g和main,抵达系统的最后一道防线——激发terminate函数.该函数调用引起运行终止的abort函数.最后一道防线的函数可以由程序员设置.从而规定其终止前的行为.
修改系统默认行为:
1).可以通过set_terminate函数修改捕捉不住异常的默认处理器,从而使得发生捉不住异常时,被自定义函数处理:
2).void myTerminate(){cout<<“HereIsMyTerminate\n”;}
3).set_terminate(myTerminate);
4).set_terminate函数在头文件exception中声明,参数为函数指针void(*)().
③异常:异常机制
构造函数没有返回类型,无法通过返回值来报告运行状态,所以只通过一种非函数机制的途径,即异常机制,来解决构造函数的出错问题。
异常机制与函数机制互不干涉,但捕捉的方式是基于类型匹配。捕捉相当于函数返回类型的匹配,而不是函数参数的匹配,所以捕捉不用考虑一个抛掷中的多种数据类型匹配问题
class A{};
class B{};
int main()
{
try{
int j = 0; double d = 2.3;
char str[20] = "Hello";
cout<<"Please input a exception number: ";
int a; cin>>a;
switch(a)
{
case 1: throw d;
case 2: throw j;
case 3: throw str;
case 4: throw A();
case 5: throw B();
default: cout<<"No throws here.\n";
}
}
catch(int){
cout<<"int exception.\n";
}
catch(double){
cout<<"double exception.\n";
}
catch(char*){
cout<<"char* exception.\n";
}
catch(A){
cout<<"class A exception.\n";
}
catch(B){
cout<<"class B exception.\n";
}
cout<<"That's ok.\n";
system("pause");
}
Tips:catch代码块必须出现在try后,并且在try块后可以出现多个catch代码块,以捕捉各种不同类型的抛掷。
异常机制是基于这样的原理:程序运行实质上是数据实体在做一些操作,因此发生异常现象的地方,一定是某个实体出了差错,该实体所对应的数据类型便作为抛掷和捕捉的依据。
异常捕捉严格按照类型匹配:异常捕捉的类型匹配之苛刻程度可以和模板的类型匹配媲美,它不允许相容类型的隐式转换,比如,抛掷char类型用int型就捕捉不到.例如下列代码不会输出“int exception.”,从而也不会输出“That’s ok.” 因为出现异常后提示退出。
④栈解旋
异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反。这一过程称为栈的解旋(unwinding)。
class MyException {};
class Test
{
public:
Test(int a=0, int b=0)
{
this->a = a;
this->b = b;
cout << "Test 构造函数执行" << "a:" << a << " b: " << b << endl;
}
void printT()
{
cout << "a:" << a << " b: " << b << endl;
}
~Test()
{
cout << "Test 析构函数执行" << "a:" << a << " b: " << b << endl;
}
private:
int a;
int b;
};
void myFunc() throw (MyException)
{
Test t1;
Test t2;
cout << "定义了两个栈变量,异常抛出后测试栈变量的如何被析构" << endl;
throw MyException();
}
void main()
{
//异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象,
//都会被自动析构。析构的顺序与构造的顺序相反。
//这一过程称为栈的解旋(unwinding)
try
{
myFunc();
}
//catch(MyException &e) //这里不能访问异常对象
catch(MyException ) //这里不能访问异常对象
{
cout << "接收到MyException类型异常" << endl;
}
catch(...)
{
cout << "未知类型异常" << endl;
}
system("pause");
return ;
}
⑤异常:接口声明
1)为了加强程序的可读性,可以在函数声明中列出可能抛出的所有异常类型,例如:
void func() throw (A, B, C , D); //这个函数func()能够且只能抛出类型A B C D及其子类型的异常。
2)如果在函数声明中没有包含异常接口声明,则次函数可以抛掷任何类型的异常,例如:
void func();
3)一个不抛掷任何类型异常的函数可以声明为:
void func() throw();
4) 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexpected函数会被调用,该函数默认行为调用terminate函数中止程序。
⑥异常类型和异常变量的生命周期
1)throw的异常是有类型的,可以使,数字、字符串、类对象。
2)throw的异常是有类型的,catch严格按照类型进行匹配。
3)注意 异常对象的内存模型 。
五、异常的层次结构
⚪异常是类 – 创建自己的异常类
⚪异常派生
⚪异常中的数据:数据成员
⚪按引用传递异常
(继承在异常中的应用) 在异常中使用虚函数
案例:设计一个数组类 MyArray,重载[]操作,
数组初始化时,对数组的个数进行有效检查
1)index<0 抛出异常eNegative
2)index = 0 抛出异常 eZero
3)index>1000抛出异常eTooBig
4)index<10 抛出异常eTooSmall
5)eSize类是以上类的父类,实现有参数构造、并定义virtual void printErr()输出错误。
六、标准程序库异常
感谢观看,希望对你有所帮助!