错误处理的方法:
1.程序终止.(例如assert断言,Linux中的段错误等)
2.返回错误码.
3.返回一个合法值,不至于让程序崩溃.(例如:服务器程序)
4.异常,返回错误信息.
异常:当一个函数发现自己无法处理的错误时就抛出异常,让程序员可以直接或者间接的去处理这个问题.
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> v;
try
{
v.at(0); //越界访问
}
catch(exception &e)
{
cout<<e.what()<<endl;
}
return 0;
}
运行的结果:invalid vector subscript
抛出了异常.
异常的抛出和捕获
在一个程序中可以多次进行异常的捕获和抛出:
例如:
void fun1()
{
try
{
vector<int> v;
v.at(0);
}
catch(exception &e)
{
throw e; //扔出异常给上一级
}
cout<<"fun1()"<<endl;
}
void fun()
{
fun1();
cout<<"fun()<<endl;"
}
int main()
{
try
{
fun();
}
catch(exception& e)
{
cout<<e.what()<<endl;
}
return 0;
}
在这个例子中,异常在fun()函数和fun1()函数中并没有对捕获的异常进行处理,所以那两个打印都没有出现.只有异常被及时处理了才会执行后面的代码,否则就会进行跳转.一直调到main()函数,如果在main()函数中也没有进行处理,那么就会终止程序
总结:
1. 异常是通过抛出对象而引发的.并且与对象的类型有关.
2. 在捕获异常时,选择就近原则,捕获类型正确的就会被捕获.下限是main()函数.
3.当在catch()中throw一个异常时,当出了函数后,throw的生命周期就会结束.但是在上一个函数依然可以捕获到throw的异常,说明在传递异常时是通过拷贝一个临时对象来传递throw的异常.(在这可以类比前面的引用返回值)
异常的捕获匹配原则
1.允许非const对象到const对象的转换.
2.允许从派生类到基类的转换.
3.数组可以转化为指向数组的指针,函数可以转化为指向函数类型的指针.
下面是一个具体的实例:
class Exception
{
public:
Exception(const string& _msg,int n)
:msg(_msg)
,id(n)
{}
virtual string What() //虚函数,构成多态
{
return msg;
}
int GetId()
{
return id;
}
protected:
string msg;
int id;
};
class SqlException:public Exception
{
public:
SqlException(const string& _msg,int n)
:Exception(_msg,n)
{}
virtual string What() //子类的重写
{
string m = "数据库错误:";
m += msg;
return m;
}
};
class NetException:public Exception
{
public:
NetException(const string& _msg,int n)
:Exception(_msg,n)
{}
virtual string What()
{
string m = "网络错误:";
m += msg;
return m;
}
};
class CacheException:public Exception
{
public:
CacheException(const string& _msg,int n)
:Exception(_msg,n)
{}
virtual string What()
{
string m = "缓存错误:";
m += msg;
return m;
}
};
void Connectsql()
{
if(rand() % 7 == 0)
{
throw SqlException("数据库连接失败\n",7);
}
}
void InitCache()
{
if(rand() % 5 == 0)
{
throw CacheException("初始化缓存\n",5);
}
}
class Server
{
public:
void StartSql()
{
Connectsql();
}
void StartCache()
{
InitCache();
}
void start()
{
while(1)
{
try
{
StartSql();
StartCache();
Sleep(100);
}
catch(Exception& e) //传引用,构成多态
{
cout<<e.What()<<endl;
}
catch(...)
{
cout<<"未知异常.\n"<<endl;
}
}
}
};
int main()
{
srand(time(0));
Server s;
s.start();
return 0;
}
异常的重新抛出
在上述的例子中,抛出的异常并不利于阅读,所以为了有更好的可读性,就可以再次封装一个类,重新抛出一个通俗易懂的异常.
异常与构造函数,异常与析构函数
构造函数的作用是初始化对象,但如果对象没有构造完成就抛出了一个异常,那么对象就会不完整了.
当构造函数完成后,如果在析构函数之前抛出了一个异常,那么就可能会造成不能析构.
例如:当new了一段空间,但是在delete的前面抛了一个异常,就会造成无法释放造成内存泄露.