对错误的处理最普遍的是采用返回错误码的形式,但是由于对错误信息的报告不是很明显,或者是只能在本层进行处理这个错误。
#iclude <stdio.h>
#include <iostream>
#include <errno>
using namespace std;
int main()
{
FILE * fd=fopen("test.txt","r");
if(fd==NULL)
{
cout<<errno<<endl;
perror("fopen");
}
return 0;
}
C++中提出了异常处理机制。
传统错误处理方式:
1.程序终止(段错误,断言)
2.返回错误码
3.返回合法值,让程序处于某种非法的状态
4.调用一个预先设置的处理错误的函数,(回调函数)
异常处理
当一个函数发现自己无法处理的错误时抛出异常,让函数的调用者直接或者间接的处理这个错误。
我们之前有了解到malloc和new的不同时,有提到说,两者对错误的处理形式不同
malloc 会返回错误码,如果申请失败,返回NULL
new是通过抛异常来报告错误
看下面一个简单的例子:
#include <iostream>
#include <string>
using namespace std;
void test()
{
try
{
int * p=new int[0xffffffff]; //由于申请空间过大会出错 //出错时抛一个对象
}
catch(exception &e)//捕获一个对象用来接收
{
cout<<e.what()<<endl;//what()其实是一个虚函数,构成多态
}
}
int main()
{
test();
return 0;
}
这里打印出错误信息
#include <iostream>
#include <stdio.h>
#include <string>
#include <errno.h>
using namespace std;
int errno;
void test()
{
int a=3;
throw a;}
void fun1()
{
try
{
test();
}
catch(exception &e)
{
cout<<e.what()<<endl;
}
cout<<"会不会走到这一步呢"<<endl;
//若没有在这一层捕获到异常,那么后面的代码就不会被执行到
}
int main()
{
try
{
fun1();
}
catch(int &e)
{
cout<<e<<endl;
}
cout<<"走到这一步了"<<endl;
//捕获之后才会正常执行后面的代码
return 0;
}
异常的抛出和捕获:
1.异常是通过抛出对象引发的,由对象的类型来决定调用哪一个处理代码;
2.被选中的代码应该是调用链中类型匹配并且是离异常抛出点最近的那一个处理代码;
3.异常抛出后会释放存储对象,所以被抛出的对象就还给操作系统了,throw表达式会会复制一个抛出对象的副本(匿名对象),交给编译器管理(这里我们看到异常的执行流是会跳出几层函数的,函数结束,对象很明显既不会存在了),并且在传给catch之后才会释放。
栈展开:
异常抛出时,将暂停当前函数的执行,先检查throw本身是否在catch中,如果是,如果是查找匹配的catch子句,如果没有匹配上的话,退出当前栈帧,继续在上一个函数栈帧中进行查找,匹配上的话就进行处理,继续执行下面的代码,否则,到main()函数中,依旧没有匹配上,则终止程序。这个沿着调用链查找到过程称为栈展开。
再看下面的例子。我们知道vector中的由操作运算符[]的重载,是vector可以像数组一样的使用,但是还有一个at()的接口,两者实现的功能是一样的,但是对错误的处理是不同的,at()处理错误就是采用的抛异常的形式,如下:
#include <iostream> #include <vector> #include <stdio.h> #include <string> #include <errno.h> using namespace std; int errno; void test() { vector<int> v1; int i=0; for(i=0;i<(int)v1.size();++i) { v1.at(i); } } void fun1() { try { test(); } catch(exception &e) { cout<<e.what()<<endl; } cout<<"会不会走到这一步呢"<<endl; } int main() { try { fun1(); } catch(int &e) { cout<<e<<endl; } cout<<"走到这一步了"<<endl; return 0; }
上面说,异常捕获需要类型匹配才可以进行的,但是有以下几个特例:
1.非const对象可以用const对象来接收
2.数组可以转换一个指向数组的指针,函数可以转换为一个指向函数的指针
3.派生类对象可以转换为基类的对象
也是由于上面的一点,一般因为异常类型的不同,要捕获的也就很多了,但是为了简化,我们一般规定抛出的异常为一个基类的派生类,捕获时只需要捕获基类类型就可以匹配上,进行异常处理,(再加上对非规范异常的捕获)
异常的重新抛出
有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再 交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上 层的函数进行处理
有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再 交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上 层的函数进行处理
#include <iostream> #include <unistd.h> #include <stdlib.h> #include <time.h> #include <vector> #include <stdio.h> #include <string> #include <errno.h> using namespace std; int errno; //定义一个基类对象 class Exception { public: Exception(const string & str,const int & errid):_errmsg(str),_errid(errid) {} //定义两个虚函数来为异常的捕获做处理 //捕获异常时进行错误信息的打印 virtual string What() { return _errmsg; } //出现异常时用于错误码的输出 virtual int Errid() { return _errid; } protected: string _errmsg; int _errid; // string _file; // int _line; // string _function; // vector<string> _stack; }; class SqlException :public Exception//这里采用公有继承,为了下面异常捕获时类型匹配时构成多态 { public:SqlException(const string & str,const int & errid):Exception(str,errid) {} virtual string What() { return _errmsg; } virtual int Errid() { return _errid; } }; class CacheException: public Exception { public:CacheException(const string & str,const int & errid):Exception(str,errid) {} virtual string What() { return _errmsg; } virtual int Errid() { return _errid; } }; void ConnectSql() { if(rand()%7==0)//模拟出错 { throw SqlException("数据库连接错误",1); } } void InitCache()//模拟出错 { if(rand()%4==0) { throw CacheException("初始化内存失败",2); } } class NetWorkServer { public: void StartSqlMar() { ConnectSql(); } void StartCacheMar() { InitCache(); } void Start() { while(1) { try { StartSqlMar(); StartCacheMar(); } catch(Exception &e)//当基类的指针或者引用才可以构成多态 { cout<<"错误编号"<<e.Errid()<<endl; cout<<e.What()<<endl; } catch(...) { cout<<"未知异常"<<endl; } usleep(100000); } } }; int main() { srand(time(0)); NetWorkServer s; s.Start(); }
#include <iostream> #include <unistd.h> #include <stdio.h> #include <vector> #include <time.h> using namespace std; void SendCore() { vector<int> v1; v1.at(0); } void Send() { //因为再本层用到的new,当中间捕获到其他异常时,就会不执行下面的delete,会造成内存泄漏 int * p1=new int(2); try { SendCore(); } catch(...)//在此处进行一次任意类型异常捕获 { delete p1;//捕获到就先处理本函数中的一些处理,并不对捕获到的异常进行处理 throw;//然后将异常重新抛出,交给上一层处理 } delete p1; } int main() { try { Send(); } catch(exception &e) { cout<<e.what()<<endl; } }
很多时候我们给用户看的错误信息都是经过异常的重新抛出来处理的,因为处理异常的信息是我们程序员应该处理的。
完。这篇总结的好累啊!