异常
1. 概念
异常事件
(如:除 0 溢出,数组下标越界,所要读取的文件不存在,空指针,内存不足等等)在C 语言对错误的处理是两种方法:
- 一是使用整型的
返回值标识错误
:- 二是使用 errno 宏(可以简单的理解为一个全局整型变量)去记录错误。
c++异常
不可忽略
(如果忽略,进程结束)。
- 异常作为一个类,可以拥有自己的成员,这些成员就可以传递足够的信息。
- 抛出异常 ----> 捕获异常。
示例:
c
int main(int argc, char *argv[])
{
int num = 10 / 0;
cout << "OVER" << endl;
return 0;
}
//不会显示OVER,程序异常结束
2. 抛出异常
语法:
throw 值或变量;
例如:
c
throw 0;
throw 1.1;
throw 'a';
throw "abc";
3. 捕获异常
语法:
try{
可能会产生异常的代码
111
222 出现异常
333
}
catch(数据类型1 变量名)
{
当throw的值与数据类型1相同进入此处
}
catch(数据类型2 变量名)
{
当throw的值与数据类型2相同进入此处
}
...
catch(...)
{
当throw的值以上数据类型都不相同进入此处
}
4. 示例
c
#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:没有释放没存
c
#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后,自动释放内存
c
#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() 括号中啥也不写,表示不允许抛出任何异常
示例:
c
#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:抛出异常对象
c
#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:抛出异常对象指针
c
#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:抛出异常对象引用 (推荐使用)
c
#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:
c
#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:重写父类虚函数
c
#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 标准异常使用
示例:
c
#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
示例:
c
#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
c
#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
c
#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;
//否则抛出异常 0
if(index < 0 || index >= size)
{
throw 0;
}
return data[index];
}
template<class X>
int ArrayList<X>::getSize()
{
return size;
}
main.cpp
c
#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>size
try{
A& a = list.get(10);
a.print();
}
catch(int e)
{
cout << "有bug" << endl;
}
return 0;
}
//有bug
11.3 示例3:读取文件
① myutils.h
c
#ifndef MYFILE_UTILS_H
#define MYFILE_UTILS_H
//声明读取文件函数
extern char *get_file_text(char *filepath);
#endif // MYFILE_UTILS_H
② filepath_error.h
c
#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
c
#include "filepath_error.h"
filepath_error::filepath_error():runtime_error("文件路径有误")
{
}
④ myutils.cpp
c
#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
c
#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;
}