服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - 一篇文章带你了解C++中的异常

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

2022-09-28 18:09是小明同学啊 C/C++

这篇文章主要为大家详细介绍了C++中的异常,使用数据库,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

异常

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

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

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

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

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

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

抛出异常基本操作

c++处理异常的优点:

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

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

在C程序中出现了异常,返回值为-1。如果C直接将-1传给B,不进行处理(也不给B报错),那么B收到-1返回值以后就会进行自己的处理,然后返回给A,然后A再进行自己的处理,那么最终程序返回的值肯定是错误的。

所以在c++中,要求必须要处理异常。如果C处理不了允许抛给B处理,B处理不了也允许抛给A处理,如果A也处理不了,那么就直接终止代码报错。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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以外的抛出类型,可以用...来接收。

?
1
2
3
4
catch (...)
    {
        cout << "其他类型异常捕获" << endl;
    }

如果捕获到了异常,但是不想处理,那么可以继续向上抛出异常。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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;

自定义的异常类

注意:类名加()就是匿名对象

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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将代码抛出去之前。所有栈上的数据会被自动的释放掉。

释放的顺序和创建的顺序是相反的。(栈:先进后出)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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之前创建了两个对象,并抛出一个匿名对象。发现在抛出去之前,两个对象就被释放了。然后抛出去的对象在程序结束时候释放。这就是栈解旋

异常接口声明

只允许抛出规定类型的异常。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//异常接口的声明
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中是不能正确执行的,都不会报错。但是在QT和linux下是可以正确执行的。

异常变量的生命周期

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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来接收对象的时候,是用的值来接收的,所以会调用拷贝构造函数。

然后就打印,并且将两个对象删除掉。

这样效率不高,如果接收对象的时候不用值来接收,而是用引用来接收,这样就能少调用一次的拷贝构造和一次析构函数。

?
1
2
3
4
catch (MyException &e)
    {
        cout << "自定义异常的捕获" << endl;
    }

运行结果:

?
1
2
3
MyException的默认构造调用
自定义异常的捕获
MyException的析构调用

还有一种方式,就是将匿名函数的地址穿进来,这样也不需要调用析构函数。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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的析构调用
自定义异常的捕获

如果传的是指针,那么匿名对象很快就会释放掉(匿名对象的特点就是执行完就释放掉),最终得到了指针也没有办法进行操作。

但如果匿名对象在=的右边,且左边还给这个对象起名了(如同上面的传对象,引用接收),那么匿名对象的寿命就会延续到左边的变量上。如果传的是指针,给指针起名和给对象起名不一样,所以就会释放。

如果不想被释放掉,还有一种方式,那就是将这个对象创建在堆区,等待着程序员自己去释放。(不会调用析构)

?
1
2
3
4
void doWork()
{
    throw new MyException();//抛出匿名对象
}

异常的多态

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//异常的基类
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++的标准异常库

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

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

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

#include <stdexcept>std:标准 except:异常

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#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()

?
1
2
3
4
5
6
const char* what() const
{
    string s;
    return s.c_str();
    //返回的就是const char*了。
}

完整代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#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的子类,不知道为啥。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注服务器之家的更多内容!                

原文链接:https://blog.csdn.net/qq_51399192/article/details/123065813

延伸 · 阅读

精彩推荐
  • C/C++C++实现聊天小程序

    C++实现聊天小程序

    这篇文章主要为大家详细介绍了C++实现聊天小程序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    圣颖君10152021-11-19
  • C/C++C++利用map实现并查集

    C++利用map实现并查集

    这篇文章主要为大家详细介绍了C++利用map实现并查集,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    y105476564912032021-09-14
  • C/C++C++继承介绍

    C++继承介绍

    C++继承可以是单一继承或多重继承,每一个继承连接可以是public,protected,private也可以是virtual或non-virtual...

    C++教程网4512020-11-15
  • C/C++Qt通过图片组绘制动态图片

    Qt通过图片组绘制动态图片

    这篇文章主要为大家详细介绍了Qt通过图片组绘制动态图片,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    weixin_457523043832021-09-15
  • C/C++VS2010+Opencv+MFC读取图像和视频显示在Picture控件

    VS2010+Opencv+MFC读取图像和视频显示在Picture控件

    这篇文章主要为大家详细介绍了VS2010+Opencv+MFC读取图像和视频显示在Picture控件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    Hello_________Word11042021-08-01
  • C/C++C语言关系运算符实例详解

    C语言关系运算符实例详解

    本文主要介绍C语言的关系运算符的知识,这里提供实例代码以便参考,希望能帮助有需要的小伙伴...

    C语言教程网8222021-04-12
  • C/C++C指针原理教程之Ncurses介绍

    C指针原理教程之Ncurses介绍

    Ncurses 提供字符终端处理库,包括面板和菜单。为了能够使用ncurses库,您必须在您的源程序中将curses.h包括(include)进来,而且在编译的需要与它连接起来. 在...

    myhaspl10492021-07-21
  • C/C++一篇文章带你了解C++的KMP算法

    一篇文章带你了解C++的KMP算法

    这篇文章主要介绍了c++ 实现KMP算法的示例,帮助大家更好的理解和学习c++,感兴趣的朋友可以了解下,希望能给你带来帮助...

    冯董事长8852021-12-16