异常
1. 概念
异常事件
(如:除 0 溢出,数组下标越界,所要读取的文件不存在,空指针,内存不足等等)在C 语言对错误的处理是两种方法:
- 一是使用整型的
返回值标识错误
:- 二是使用 errno 宏(可以简单的理解为一个全局整型变量)去记录错误。
c++异常
不可忽略
(如果忽略,进程结束)。
- 异常作为一个类,可以拥有自己的成员,这些成员就可以传递足够的信息。
- 抛出异常 ----> 捕获异常。
示例:
int main(int argc, char *argv[])
{ int num = 10 / 0;cout << "OVER" << endl;return 0;
}
//不会显示OVER,程序异常结束
2. 抛出异常
语法:
throw 值或变量;
例如:
throw 0;throw 1.1;throw 'a';throw "abc";
3. 捕获异常
语法:
try{可能会产生异常的代码111222 出现异常333
}
catch(数据类型1 变量名)
{当throw的值与数据类型1相同进入此处
}
catch(数据类型2 变量名)
{当throw的值与数据类型2相同进入此处
}
...
catch(...)
{当throw的值以上数据类型都不相同进入此处
}
4. 示例
#include <iostream>using namespace std;class A{};
void my_error(int a, int b)
{if(b == 0){//抛出异常//throw 0;//throw 1.2;//throw 'a';//throw "abc";throw new A();}cout << a / b << endl;
}
void fun01()
{try{my_error(10, 0);}catch(int e){cout << "int抛出的异常值为:" << e << endl;}catch(double e){cout << "double抛出的异常值为:" << e << endl;}catch(char e){cout << "char抛出的异常值为:" << e << endl;}catch(...){cout << "除以上类型外所有异常" << endl;}
}int main(int argc, char *argv[])
{fun01();cout << "Hello World!" << endl;return 0;
}
//除以上类型外所有异常
//Hello World!
5. 栈解旋
概念:
异常被抛出后,从进入 try 块起,到异常被抛掷前,这期间 在栈上构造的所有对象,都会被自动析构。
析构的顺序与构造的顺序相反,这一过程称为栈的解旋 。
问题:在创建对象的过程中,抛出异常,此时 还 没来的及释放对象
,所以会出现错误。
解决办法:用try…catch 捕获异常,就会自动释放内存空间
示例1:没有释放没存
#include <iostream>using namespace std;
class B{
private:int x;
public:B(int x){this->x = x;cout << "B." << x << "被创建了" << endl;}~B(){cout << "B." << x << "被销毁了" << endl;}
};
void fun02()
{B b1(1);B b2(2);B b3(3);//抛出异常throw 0;
}int main(int argc, char *argv[])
{fun02();cout << "Hello World!" << endl;return 0;
}
示例2:使用try…catch后,自动释放内存
#include <iostream>using namespace std;
class B{
private:int x;
public:B(int x){this->x = x;cout << "B." << x << "被创建了" << endl;}~B(){cout << "B." << x << "被销毁了" << endl;}};
void fun02()
{B b1(1);B b2(2);B b3(3);//抛出异常throw 0;
}int main(int argc, char *argv[])
{try{fun02();}catch(int e){}cout << "Hello World!" << endl;return 0;
}
6. 异常的接口声明
作用:限定异常抛出的类型
语法:
返回值类型 函数名(形参列表)throw(数据类型1,数据类型2,...)
{函数体
}
注意:
- 声明异常后,当前函数中只能抛出指定类型的异常
- throw() 括号中啥也不写,表示不允许抛出任何异常
示例:
#include <iostream>using namespace std;class A{};void my_error(int a, int b)
{if(b == 0){//抛出异常//throw 0;//throw 1.2;//throw 'a';//throw "abc";throw new A();}cout << a / b << endl;
}
void fun01()
{try{my_error(10, 0);}catch(int e){cout << "int抛出的异常值为:" << e << endl;}catch(double e){cout << "double抛出的异常值为:" << e << endl;}catch(char e){cout << "char抛出的异常值为:" << e << endl;}catch(...){cout << "除以上类型外所有异常" << endl;}
}class B{
private:int x;
public:B(int x){this->x = x;cout << "B." << x << "被创建了" << endl;}~B(){cout << "B." << x << "被销毁了" << endl;}};
void fun02()
{B b1(1);B b2(2);B b3(3);//抛出异常throw 0;
}
//当前函数只能抛出int或char类型的异常
//void fun03() throw(int,char)
//throw() 说明当前函数不会抛出任何异常
void fun03() throw()
{throw 0;
}
int main(int argc, char *argv[])
{
// try
// {
// fun03();
// }
// catch(int e)
// {
// }fun03();cout << "Hello World!" << endl;return 0;
}
// 此时会报错
//terminate called after throwing an instance of 'int'
//抛出'int'实例后调用终止
7. 异常对象的生命周期
- 抛出异常对象
- 会
多次
调用对象的构造和析构
- 会
- 抛出异常对象指针
- 只调用 构造函数,没有析构
- 抛出异常对象引用 (推荐使用)
- 只会调用一次构造,一次析构
- 注意:
隐式创建
对象,不然会触发拷贝构造
7.1 示例1:抛出异常对象
#include <iostream>using namespace std;
class MyError
{
public:MyError(){cout << "构造函数" << endl;}MyError(const MyError& e){cout << "拷贝构造" << endl;}~MyError(){cout << "析构函数" << endl;}
};void test01()throw(MyError)
{throw MyError(); //调用构造
}
void fun01()
{try{test01();}catch(MyError e) //调用拷贝构造{}
}int main(int argc, char *argv[])
{fun01();return 0;
}
//构造函数
//拷贝构造
//析构函数
//析构函数
注意:显示创建对象 会调用 构造和拷贝构造
7.2 示例2:抛出异常对象指针
#include <iostream>using namespace std;
class MyError
{
public:MyError(){cout << "构造函数" << endl;}MyError(const MyError& e){cout << "拷贝构造" << endl;}~MyError(){cout << "析构函数" << endl;}
};void test02()
{//new 返回的是error对象的指针throw new MyError();
}void fun02()
{try{test02();}catch(MyError *e){}
}int main(int argc, char *argv[])
{fun02();return 0;
}
//构造函数
7.3 示例3:抛出异常对象引用 (推荐使用)
#include <iostream>using namespace std;
class MyError
{
public:MyError(){cout << "构造函数" << endl;}MyError(const MyError& e){cout << "拷贝构造" << endl;}~MyError(){cout << "析构函数" << endl;}
};void test03()
{throw MyError();
}void fun03()
{try{test03();}catch(MyError& e){}
}
int main(int argc, char *argv[])
{fun03();return 0;
}
//构造函数
//析构函数
8. 异常的多态
概念:子类异常对象 可以被 父类异常类型捕获 ,原理是上行,子传父,多态。
示例1:
#include <iostream>using namespace std;class BaseException{};
class MyException01:public BaseException{};
class MyException02:public BaseException{};
void test05()
{try{throw MyException01();}catch(BaseException){cout << "可以捕获子类异常" << endl;}
}
int main(int argc, char *argv[])
{test05();return 0;
}
//可以捕获子类异常
示例2:重写父类虚函数
#include <iostream>using namespace std;
class BaseException{
public:virtual void printMsg(){}
};
class NullException:public BaseException{
public:void printMsg(){cout << "空指针异常" << endl;}
};
class ArrOutException:public BaseException{
public:void printMsg(){cout << "数组下标越界异常" << endl;}
};
void test05()
{try{throw NullException();}catch(BaseException &e){e.printMsg();}
}
int main(int argc, char *argv[])
{test05();return 0;
}
//空指针异常
9. 标准异常库
9.1 简介
标准库中也提供了很多的 异常类
,它们是通过类 继承组织起来
的。
异常类继承层级结构图所示 :
标准异常类的成员:
① 在上述继承体系中,每个类都有提供了构造函数、复制构造函数、和赋值操作符重载。
② logicerror 类及其子类、 runtimeerror 类及其子类,它们的构造函数是接受一个string 类型的形式参数,用于异常信息的描述
③ 所有的异常类都有一个 what()
方法,返回 const char*
类型(C 风格字符串)的值,描述异常信息。
标准异常类的具体描述:
异常名称 | 描述 |
---|---|
exception | 所有标准异常类的父类 |
bad_alloc | 当 operator new and operator new[],请求分配内存失败时 |
bad_exception | 这是个特殊的异常,如果函数的异常抛出列表里声明了 badexception 异常,当函数内部抛出了异常抛出列表中没有的异 常,这是调用的 unexpected 函数中若抛出异常,不论什么类 型,都会被替换为 badexception 类型 |
bad_typeid | 使用 typeid 操作符,操作一个 NULL 指针,而该指针是带有虚函数的类,这时抛出 bad_typeid 异常 |
bad_cast | 使用 dynamic_cast 转换引用失败的时候 |
ios_base::failure | io 操作过程出现错误 |
logic_error | 逻辑错误,可以在运行前检测的错误 |
runtime_error | 运行时错误,仅在运行时才可以检测的错误 |
logic_error
的子类:
异常名称 | 描述 |
---|---|
length_error | 试图生成一个超出该类型最大长度的对象时,例如 vector 的 resize 操作 |
domain_error | 参数的值域错误,主要用在数学函数中。例如使用一个负值调 用只能操作非负数的函数 |
outofrange | 超出有效范围 |
invalid_argument | 参数不合适。在标准库中,当利用 string 对象构造 bitset 时, 而 string 中的字符不是’0’或’1’的时候,抛出该异常 |
runtime_error
的子类:
异常名称 | 描述 |
---|---|
range_error | 计算结果超出了有意义的值域范围 |
overflow_error | 算术计算上溢 |
underflow_error | 算术计算下溢 |
invalid_argument | 参数不合适。在标准库中,当利用 string 对象构造 bitset 时, 而 string 中的字符不是’0’或’1’的时候,抛出该异常 |
9.2 标准异常使用
示例:
#include <iostream>using namespace std;
void test02()
{//throw runtime_error("使用系统提供的运行时异常类");throw logic_error("逻辑错误");
}void fun02()
{try{test02();}
// catch(runtime_error &e)
// {
// const char * msg = e.what();
// cout << msg << endl;
// }catch(exception &e){const char * msg = e.what();cout << msg << endl;}
}int main(int argc, char *argv[])
{fun02();cout << "Hello World!" << endl;return 0;
}
//逻辑错误
10. 自定义异常
步骤:
- 定义一个类
- 继承与异常类
- 重写wait方法
方式1: 继承总异常类 exception 需要冲写what() 函数
方式2: 继承总异常类Exception下的某一个 异常类,此处是 runtime_error
示例:
#include <iostream>
using namespace std;
//方式1: 继承总异常类 exception 需要冲写what() 函数
class my_error:public exception{
private://记录异常信息char *msg;
public:my_error(char *msg){this->msg = msg;}//重写父类 what() 函数const char* what() const _GLIBCXX_USE_NOEXCEPT{return msg;}
};//方式2: 继承总异常类Exception下的某一个 异常类,此处是 runtime_error
class my_error02:public runtime_error{
public:my_error02(char *msg):runtime_error(msg){}
};
//调用方式1
void test()
{throw my_error("自定义异常");
}
//调用方式2
void test02()
{throw my_error02("自定义异常2");
}
void fun01()
{try{test02();}catch(exception &e){cout << e.what() << endl;}
}int main(int argc, char *argv[])
{fun01();return 0;
}
//自定义异常2
11. 练习总结
11.1 示例1
#include <iostream>
#include <cstdio>
using namespace std;void test03(int a,int b)
{if(b == 0){throw "除数不能为0";}cout << a / b << endl;
}void test04()
{FILE* f = fopen("xxx","r");if(f == NULL){throw "文件路径不正确";}
}void fun03()
{try{test03(10,0);}catch(char const* e){cout << e << endl;}
}
void fun04()
{try{test04();}catch(char const* e){cout << e << endl;}
}
int main(int argc, char *argv[])
{fun03();fun04();return 0;
}
//除数不能为0
//文件路径不正确
11.2 示例2:ArrayList
arraylisat.hpp
#include <cstring>
template<class X>
class ArrayList{X *data;int size;int count;
public:ArrayList();~ArrayList();void add(X& x);X& get(int index);int getSize();
};template<class X>
ArrayList<X>::ArrayList()
{data = new X[2];count = 2;size = 0;
}template<class X>
ArrayList<X>::~ArrayList()
{delete [] data;
}template<class X>
void ArrayList<X>::add(X& x)
{if(size == count){count *= 2;X* newData = new X[count];memcpy(newData,data,size*sizeof(X));delete [] data;data = newData;}data[size] = x;size++;
}template<class X>
X& ArrayList<X>::get(int index)
{//判断传入的参数是否合规,应该 >0,<size;//否则抛出异常 0if(index < 0 || index >= size){throw 0;}return data[index];
}template<class X>
int ArrayList<X>::getSize()
{return size;
}
main.cpp
#include <iostream>
#include "arraylist.hpp"using namespace std;class A{int num;
public:A(){}A(int num):num(num){}void print(){cout << num << endl;}
};
int main(int argc, char *argv[])
{ArrayList<A> list;A d01 = 1;A d02 = 2;A d03 = 3;A d04 = 4;A d05 = 5;list.add(d01);list.add(d02);list.add(d03);list.add(d04);list.add(d05);//捕获异常,此时ArrayList中只有5个数据,参数10>sizetry{A& a = list.get(10);a.print();}catch(int e){cout << "有bug" << endl;}return 0;
}
//有bug
11.3 示例3:读取文件
① myutils.h
#ifndef MYFILE_UTILS_H
#define MYFILE_UTILS_H//声明读取文件函数
extern char *get_file_text(char *filepath);
#endif // MYFILE_UTILS_H
② filepath_error.h
#ifndef FILEPATH_ERROR_H
#define FILEPATH_ERROR_H
#include <iostream>
//定义路径错误函数,继承runtime_error
#include <iostream>
using namespace std;
class filepath_error:public runtime_error
{
public:filepath_error();
};
#endif // FILEPATH_ERROR_H
③ filepath_error.cpp
#include "filepath_error.h"filepath_error::filepath_error():runtime_error("文件路径有误")
{}
④ myutils.cpp
#include <iostream>
#include <cstdio>
#include <cstring>
#include "myfileutils.h"
#include "filepath_error.h"//根据文件名读取文件中的内容
char *get_file_text(char *filepath)
{FILE *f = fopen(filepath, "r");if(f == NULL){//throw 0;throw filepath_error();}//计算文本长度fseek(f, 0, 2);int len = ftell(f);//创建存放读取的文件的数组char *c = new char[len];//将数组数据置零memset(c, 0, len);//游标恢复置开始fseek(f, 0, 0);//读取数据fread(c, len, 1, f);fclose(f);return c;}
//甲乙丙丁戊己庚辛壬癸
//子丑寅卯陈思武威申酉戌亥
⑤ main.cpp
#include <iostream>
#include "myfileutils.h"
#include "filepath_error.h"using namespace std;int main(int argc, char *argv[])
{try{//char *content = get_file_text("D:/io");char *content = get_file_text("D:/a.txt");cout << content << endl;}catch(exception &e){cout << e.what() << endl; //文件路径有误}return 0;
}