异常
在c语言中,对错误的处理总是两种方法:
1,使用整型的返回值表示错误(有时候用1表示正确,0表示错误;有的时候0表示正确,1表示错误)
2,使用errno宏(可以简单理解为一个全局整形变量)去记录错误。(如果错误,就将被改变的全局整形变量返回)
c++中仍然可以用上面的两种方法,但是有缺点。
(1)返回值不统一,到底是1表示正确,还是0表示正确。
(2)返回值只有一个,通过函数的返回值表示错误代码,那么函数就不能返回其他的值。(输出的值到底是表示异常的值-1,还是最后在那个结果就是-1呢)
抛出异常基本操作
c++处理异常的优点:
异常处理可以带调用跳级。
在C程序中出现了异常,返回值为-1。如果C直接将-1传给B,不进行处理(也不给B报错),那么B收到-1返回值以后就会进行自己的处理,然后返回给A,然后A再进行自己的处理,那么最终程序返回的值肯定是错误的。
所以在c++中,要求必须要处理异常。如果C处理不了允许抛给B处理,B处理不了也允许抛给A处理,如果A也处理不了,那么就直接终止代码报错。
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以外的抛出类型,可以用...
来接收。
catch (...)
{
cout << "其他类型异常捕获" << endl;
}
如果捕获到了异常,但是不想处理,那么可以继续向上抛出异常。
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;
}
自定义的异常类
注意:类名加()就是匿名对象
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将代码抛出去之前。所有栈上的数据会被自动的释放掉。
释放的顺序和创建的顺序是相反的。(栈:先进后出)
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之前创建了两个对象,并抛出一个匿名对象。发现在抛出去之前,两个对象就被释放了。然后抛出去的对象在程序结束时候释放。这就是栈解旋
异常接口声明
只允许抛出规定类型的异常。
//异常接口的声明
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下是可以正确执行的。
异常变量的生命周期
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来接收对象的时候,是用的值来接收的,所以会调用拷贝构造函数。
然后就打印,并且将两个对象删除掉。
这样效率不高,如果接收对象的时候不用值来接收,而是用引用来接收,这样就能少调用一次的拷贝构造和一次析构函数。
catch (MyException &e)
{
cout << "自定义异常的捕获" << endl;
}
运行结果:
MyException的默认构造调用
自定义异常的捕获
MyException的析构调用
还有一种方式,就是将匿名函数的地址穿进来,这样也不需要调用析构函数。
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的析构调用
自定义异常的捕获
如果传的是指针,那么匿名对象很快就会释放掉(匿名对象的特点就是执行完就释放掉),最终得到了指针也没有办法进行操作。
但如果匿名对象在=的右边,且左边还给这个对象起名了(如同上面的传对象,引用接收),那么匿名对象的寿命就会延续到左边的变量上。如果传的是指针,给指针起名和给对象起名不一样,所以就会释放。
如果不想被释放掉,还有一种方式,那就是将这个对象创建在堆区,等待着程序员自己去释放。(不会调用析构)
void doWork()
{
throw new MyException();//抛出匿名对象
}
异常的多态
//异常的基类
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++的标准异常库
标准库中提供了很多的异常类,它们是通过类继承组织起来的。
如果使用系统提供的标准异常的时候,需要调用规定的头文件
#include <stdexcept>
std:标准 except:异常
#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()
const char* what() const
{
string s;
return s.c_str();
//返回的就是const char*了。
}
完整代码:
#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的子类,不知道为啥。
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注编程网的更多内容!