异常机制
C语言的错误处理
-
通过返回值来表示,需要返回特殊的值
-
fopen malloc 返回NULL表示出错
-
fread fwrite 返回0表示出错,非0表示正确读写次数
-
缺陷:
- 层层判断函数返回结果,且正常代码逻辑和错误处理逻辑代码混在一起的,如果有多种错误,还需要为每一个错误设定一个特殊返回值
- 有些函数正常的返回值就可以取到任意结果,这种情况就无法用特殊值来表示错误
-
当调用C语言标准库 中的函数或者系统调用失败之后,设置全局的errno,获取错误信息的方式有:
#include <errno.h> extern int errno; #include <string.h> char *strerror(int errno); //通过errno错误码获取对应的错误信息,只有调用失败才会修改errno的值,即errno只能通过判断函数的返回值确定函数调用失败之后用errno来获取错误信息,不能直接通过errno!=0判断成功 const char *perrno(const char *str);// //printf %m 格式占位符 自动用strerror(errno)来获取对应的错误信息
-
-
setjmp/longjmp
//#include <setjmp.h> int setjmp(jmp_buf env); void long jmp(jmp_buf env, int val); //jmp_buf是一个类型,每使用一次,需要重新定义一个jmp_buf类型的全局变量 //setjmp首次调用返回的一定为0,当调用该函数时,会跳转到setjmp函数处,冰使得setjmp返回val(非0)值 int singsetjmp(jmp_buf env, int savesigs); void singlongjmp(jmp_buf env, int val);
- 在C++中局部对象无法得到析构,可能会产生内存泄漏
C++的错误处理方式
可以使用C语言的错误处理方式,但都会有缺陷,所以C++推荐使用异常机制
- C++在抛出异常前, 会对局部对象进行析构,保证内存资源不会泄漏
异常机制
-
程序出错之后,可以throw抛出异常,在那之前,会对所有的局部变量进行析构,同时从抛出异常的代码中直接跳出,去匹配最近层次的try-catch语句,根据异常的类型来匹配catch分配中的异常,如果遗产的对象is a catch 中的异常类型的对象,则进入catch分支中进行异常处理,异常处理完成后,继续从最后一个catch分支继续进行,如果catch到了异常,但无法处理,则可以继续往外抛出,一个异常要么被捕获,要么一直往外抛出,直到被系统捕获,执行默认处理——显示异常信息,终止程序执行
-
语法:
try{ //可能发生异常的语句块; }catch(异常类型& e1){ }catch(异常类型& e2){ }catch(异常类型& e3){ }catch(...){ //如果上面没有匹配的异常类型,一定会捕获异常,进入该分支 } /* 在一个完整的try-catch语句中,如果出现异常,会重上而下的对catch进行匹配,若有则进入该分支,如果异常类型有继承的话,则子类异常应该在父类异常catch之前执行catch。可以在崔后加上catch(...)确保所有异常都被捕获 */
标准异常
- 所有的标准异常都直接或者间接继承至 std::exception 类
- logic_error
- out_of_range
- invalid_argument
- runtime_error
- range_error
- overflow_error
- underflow_error
- bad_typeid
- bad_cast
- bad_alloc
C++中一般用不同的异常类型类表示不同的错误
-
C++也允许用一种类型不同的值来表示不同的错误
-
C++中也允许使用任意类型来表示异常
C++中可以自己抛出异常
throw 异常类型(实参列表);
throw是一个关键字,可以抛出异常
C++的异常处理流程
- 正常逻辑代码和异常是分开处理d
- 局部对象能正确析构
- 每一层只捕获自己能处理的异常,不能处理的继续往外抛出
- 如果要确保捕获所有类型的异常,可以使用catch(…)
- 如果有没有补货的异常,则会交给系统做默认处理,(显示异常信息,终止程序执行
- 程序员可以自己定义异常类型,一般都会遵循直接或间接继承expection类的原则,并且重写what()
- 特殊情况下,catch了异常但是无法处理可以继续往外抛出
//拥有异常机制的栈
#include <iostream>
#include <stdexcept>
using namespace std;
class StackOverFlow:public exception{
public:
const char *what(){
return "stack over flow";
}
};
class StackUnderFlow:public exception{
public:
const char *what(){
return "stack under flow";
}
};
class Stack{
public:
Stack(size_t cap):cap(cap),cnt(0){
elems = new int[cap];
}
~Stack(){
delete [] elems;
}
bool full()const{
return cap == cnt;
}
bool empty()const{
return cnt == 0;
}
void push(int elem){
if(full()){
throw StackOverFlow();
}
elems[cnt++] = elem;
}
void pop(void){
if(empty()){
throw StackUnderFlow();
}
--cnt;
}
int top()const{
if(empty()){
throw StackUnderFlow();
}
return elems[cnt-1];
}
private:
int *elems;
size_t cap;
size_t cnt;
};
int main(int argc,char *argv[]){
Stack s(10);
try{
int i;
for(i=0;i<1000;++i){
s.push(i);
}
}catch(StackOverFlow& e){
cout << e.what() << endl;
}
try{
while(true){
cout << s.top() << endl;
s.pop();
}
}catch(StackUnderFlow& e){
cout << e.what() << endl;
}
return 0;
}
输出:
stack over flow
9
8
7
6
5
4
3
2
1
0
stack under flow
-
自定义异常类型
函数的异常说明
函数返回值类型 函数名(形参列表) throw (异常类型...){
//
}
函数的异常说明表示的是该函数中输出的异常可以被捕获的异常有哪些类型,除了异常说明意外所有抛出的异常类型无法被catch
往往用于说明一和函数可能抛出的异常种类,在try-catch时,只需要catch异常说明中的异常类型即可
一个函数如果没有异常说明,则表示该函数可以抛出任意的异常,且所有的异常都可以被捕获
C++11怎加了noexcept说明符,表示该函数不抛出任何异常, 如果出现异常,则该异常无法被catch
在重写覆盖中,子类重写版本函数的异常说明,不能比父类版本有更多的异常说明
构造函数的异常
- 如果一个函数在构造过程中产生了异常,则该对象称为构造不完全对象,该对象时无法进行析构的
- 所以如果有此异常,需要在构造函数中自行捕获处理(如果在异常产生前,有申请动态内存,需要手动释放)
- 永远不要在析构函数抛异常