异常处理
C语言如何来做错误处理
通过返回值来判断
- 返回 true/false
- 返回 ErrorNo/ErrorCode
在方法内的错误处理
- do{ ... } while(false);
- goto 极度不推荐
setjmp 和 longjmp
- 看个例子
#include <stdio.h>
jup_buf env;
double Div(double d1, double d2)
{
double* d = (double*)malloc(10);
if (d2 == 0.0) // 这个判断是简写, 表示一个意思, 这样判断是错误的
{
longjmp(env, 1); // 这不是一个返回 是一个跳转
}
free(d); // 如果发生了longjmp 内存泄露了
return d1 / d2;
}
void Foo()
{
longjmp(env, 2);
}
int main()
{
int ret = 0;
ret = setjmp(env);
if (ret == 0)
{
Div(5.0, 1.0);
Div(5.0, 0.0);
}
else if (ret == 1)
{
printf("Div Error!");
Foo();
}
else if (ret == 2)
{
printf("Foo Error!");
}
return 0;
}C++的错误处理方式
错误我们分为2种
编译错误(语法错误)
- 这种编译器会进行提示.
运行时错误(语义错误)
- 打开文件失败
- 被除数为0
- 内存不足
- .....
C++中, 为了处理运行时错误, 增加了_异常体系_
异常体系
- runtime error
- logic error
try catch的小例子
#include <stdio.h>
jup_buf env;
double Div(double d1, double d2)
{
double* d = new double(0.0);
if (d2 == 0.0) // 这个判断是简写, 表示一个意思, 这样判断是错误的
{
throw 1;
// throw "除数不能为0" // 放开这一句 注释上一句试试
}
free(d); // 如果发生了longjmp 内存泄露了
return d1 / d2;
}
void Foo()
{
throw 2;
// throw "就是任性, 就是错!" // 放开这一句 注释上一句试试
}
int main()
{
int ret = 0;
try
{
Div(5.0, 1.0);
Div(5.0, 0.0);
Foo();
}
catch (int e)
{
if (e == 1)
{
printf("Div Error!");
}
else if (ret == 2)
{
printf("Foo Error!");
}
}
catch (char* sz) // catch (void* sz)也可以catch到throw的话
{
printf("%s\n", sz);
}
return 0;
}throw 可以抛出任意数据类型, 可以是char * , 可以是int, 可以是double, 也可以是一个class
catch 只能根据_数据类型_来进行捕获, 类型不相同将不能捕捉到, 也_不会_进行_自动转换_以适配捕获
catch ( ... ) 可以捕获所有类型异常
try 包含的是可能出异常的代码
catch中可以直接throw, 表示将捕获的异常原封不动的抛出
try catch throw 是_栈安全机制_的.
函数层层调用, 在最后一层throw后, 它会_按照函数调用顺序层层返回_, 直到catch.
如果没有catch, 则会交由编译器进行处理, 一般会抛到程序外
throw会进行_栈_的销毁, 会释放我们的局部变量.
堆上的数据不会自动销毁, 需要在throw之前进行手动清理
一个清理栈的例子:
#include <iostream>
#include <string>
using namespace std;
class MyException
{
public:
MyException(const string& sz);
void What() const;
private:
string sz_;
};
MyException::MyException(const string& sz) : sz_(sz) {}
void MyException::What() const
{
cout << sz_ << endl;
}
class MyOtherException : public MyException
{
public:
MyOtherException(string sz) : MyException(sz) {}
};
class Test
{
public:
Test(int id) : id_(id)
{
cout << "Test()" << id_ << endl;
}
~Test()
{
cout << "~Test()" << id_ << endl;
}
Test(const Test& test)
{
cout << "Test(const Test&)" << id_ << " " << test.id_ << endl;
}
private:
int id_;
};
Test& Foo4()
{
Test* tt = new Test(2);
return *tt;
}
void Foo3()
{
Test t(1);
Test t1 = Foo4();
throw MyException("Foo3");
}
void Foo2(Test test)
{
Foo3();
}
void Foo1(Test& test)
{
Foo2(test);
}
int main()
{
try
{
Foo1(Test(0));
}
catch (MyException e)
{
e.What();
}
catch (MyOtherException e) // 如果在基类之后 它将不工作
{
e.What();
}
return 0;
}运行结果: 
结果解析:
在调用Foo1的时候 新建了对象Test0
Foo1调用Foo2, 传递对象Test0, 由Foo2在接收参数的时候, 拷贝构造了Test0
Foo2调用Foo3, Foo3新建对象Test1
Foo3调用Foo4, Foo4新建了对象Test2
Foo3接收Foo4的对象时, 拷贝构造了TestXXXX
异常发生, 开始清理栈
首先清理本函数内的栈, 析构了新拷贝构造的TestXXXX
然后清理本函数内的Test1
然后方法返回Foo2, 继续清理Foo2的栈
清理了拷贝构造的Test0
然后返回至Foo1, 清理Foo1的栈
清理了Foo1新建的Test0
遇到了catch, 异常被捕获, 栈区清理结束, 开始新的代码执行.
而我们在Foo4中new的对象由于不在栈内在堆内, 内存被泄露. 如上行为, 我们称之为栈展开
自定义异常类的继承
继承方法与普通的类继承无异.
在catch的时候, 一定要_先catch派生类_, 然后在catch基类
非迫不得已的时候, 永远不要抛出指针
- 因为指向栈的指针会被清理掉
- 在抛出指针的时候, 任何一个指针都可以呗void * 进行catch
- 如果要抛出指针, 就抛出一个指向堆的指针
构造函数和析构函数中throw
可以在构造函数中throw
永远不要在_析构函数_中使用throw, 否则程序会中止
因为析构函数在异常处理的时候会被调用
C++的异常类
std::exception
std::runtime_error // 运行时错误
- std::overflow_error
- std::range_error
- std::underflow_error
std::logic_error // 逻辑错误
- std::invalid_argument
- std::length_error
- std::out_of_range // 超出范围
- std::domain_error
- 以上异常, 通常使用...来进行捕获.
- std::bad_alloc // 内存分配出错
- std::bad_cast // 转换出错
一般我们自己的异常类, 都继承自 std::exception
一般我们都是先捕获自己的异常类, 然后在使用_..._或者_std::exception_捕获其他异常.
冷知识
在一些方法后, 我们可能会见到如下关键字:
- noexception // 表示方法不会抛出异常
- throw() // 表示方法会抛出异常, 但是不知道抛出什么异常
- noexception(true) // 表示方法会抛出异常
如有错误,请提出指正!谢谢.
本文由 花心胡萝卜 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: 2017-02-24 at 03:22 pm