C语言处理错误的方式
- 终止程序,如assert、exit等
- 返回错误码,这种方式比较常用,但是需要查找对应的错误,不够直观
C++中的异常
异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者处理这个错误
- throw: 当问题出现时,程序会抛出一个异常。通过使用 throw 关键字来完成
- catch: 在想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常,可以有多个catch进行捕获
- try: try 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块
异常的抛出和捕获
抛出一个已知类型的异常进行捕获:
double Division(int a, int b)
{
// 当b == 0时抛出异常;异常抛出后就必须进行捕获
if (b == 0)
throw "除数不能为0"; // 抛出的异常可以是任意类型的对象,此处为常量字符串指针
else
return (a / b);
}
void Func()
{
int a, b;
cin >> a >> b;
cout << Division(a, b) << endl;
}
int main()
{
try {
Func();
}
catch (const char* errmsg) // 捕获抛出的字符串指针
{
cout << errmsg << endl; // 对其及进行输出
}
return 0;
}
再抛一个任意类型的异常进行捕获:
double Division(int a, int b)
{
// 当b == 0时抛出异常;异常抛出后就必须进行捕获
if (b == 0)
throw 4; // 抛出的异常可以是任意类型的对象,此处抛出一个整形,假设此异常类型未知
else
return (a / b);
}
void Func()
{
int a, b;
cin >> a >> b;
cout << Division(a, b) << endl;
}
int main()
{
try {
Func();
}
catch (...) // 捕获任意类型的异常,防止程序异常终止,但这样捕获不知道具体的错误
{
cout << "未知异常" << endl;
}
return 0;
}
异常的重新抛出
先来看段代码:
double Division(int a, int b)
{
// 当b == 0时抛出异常;异常抛出后就必须进行捕获
if (b == 0)
throw "除数不能为0"; // 抛出的异常可以是任意类型的对象,此处为常量字符串指针
else
return (a / b);
}
void Func()
{
int* array = new int[10]; // new一个数组
int a, b;
cin >> a >> b;
cout << Division(a, b) << endl;
delete[] array; // delete数组
}
int main()
{
try {
Func();
}
catch (const char* errmsg) // 捕获抛出的字符串指针
{
cout << errmsg << endl; // 对其及进行输出
}
return 0;
}
//在上面这段代码中,运行main函数时先执行Func函数,Func函数先new了一个数组,再调用执行Division函数
//若执行Division函数时发生异常,则会直接跳转到catch处去捕获异常,这时在Func中new的数组并没有delete
//这就造成了内存泄漏问题
此时可以在中间捕获,处理完再抛出:
double Division(int a, int b)
{
// 当b == 0时抛出异常;异常抛出后就必须进行捕获
if (b == 0)
throw "除数不能为0"; // 抛出的异常可以是任意类型的对象,此处为常量字符串指针
else
return (a / b);
}
void Func()
{
int* array = new int[10]; // new一个数组
try // 把可能发生异常的代码放到try模块里
{
int a, b;
cin >> a >> b;
cout << Division(a, b) << endl;
}
catch (const char* errmsg) // 捕获--这里的捕获并不是为了处理异常,而只是为了重新抛出
{
delete[] array; // 处理内存泄漏问题
throw errmsg; // 重新抛出异常
}
delete[] array; // delete数组
}
int main()
{
try {
Func();
}
catch (const char* errmsg) // 捕获抛出的字符串指针
{
cout << errmsg << endl; // 对其及进行输出
}
return 0;
}
若上面的try模块可能会抛出多种类型的异常,则需要与之对应的写多种catch模块去捕获不同种类的异常。
因此重新抛出的场景更建议下面这种写法:
double Division(int a, int b)
{
// 当b == 0时抛出异常;异常抛出后就必须进行捕获
if (b == 0)
throw "除数不能为0"; // 抛出的异常可以是任意类型的对象,此处为常量字符串指针
else
return (a / b);
}
void Func()
{
int* array = new int[10]; // new一个数组
try // 把可能发生异常的代码放到try模块里
{
int a, b;
cin >> a >> b;
cout << Division(a, b) << endl;
}
// 捕获任意类型的异常,在模块内抛出
catch (...) // 捕获--这里的捕获并不是为了处理异常,而只是为了重新抛出
{
delete[] array; // 处理内存泄漏问题
throw ; // 重新抛出异常
}
delete[] array; // delete数组
}
int main()
{
try {
Func();
}
catch (...) // 捕获任意类型的异常,防止程序异常终止,但这样捕获不知道具体的错误
{
cout << "未知异常" << endl;
}
return 0;
}
异常安全
有些地方抛异常可以解决问题,而有些地方抛异常可能会带来问题。
- 在构造函数中抛异常,可能会导致初始化不完整
- 在析构函数中抛异常可能会导致资源清理不完全,从而引发内存泄漏
- 在new和delete中间抛异常,可能会导致内存泄漏
- 在lock和unlock之间抛异常,可能会导致死锁问题
异常的优缺点
优点:
- 相比错误码的方式可以清晰准确的展示出错误的各种信息,这样可以帮助更好的定位程序的bug
- 部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码方式处理
缺点:
- 异常会导致程序的执行流乱跳,非常的混乱;运行时出错抛异常会乱跳,导致跟踪调试以及分析程序时比较困难
- 有了异常非常容易导致内存泄漏、死锁等异常安全问题