|
异常处理就是处理程序中的错误。所谓错误是指在程序运行的过程中发生的一些异常事件(如:除0溢出,数组下标越界,所要读取的文件不存在,空指针,内存不足等等)。在C语言的世界中,对错误的处理总是围绕着两种方法:
- 一是使用整型的返回值标识错误;
- 二是使用errno宏(可以简单的理解为一个全局整型变量)去记录错误。当然C++中仍然是可以用这两种方法的。
这两种方法最大的缺陷就是会出现不一致问题。例如有些函数返回1表示成功,返回0表示出错;而有些函数返回0表示成功,返回非0表示出错。
还有一个缺点就是函数的返回值只有一个,你通过函数的返回值表示错误代码,那么函数就不能返回其他的值。当然,你也可以通过指针或者C++的引用来返回另外的值,但是这样可能会令你的程序略微晦涩难懂。
1、异常的基本语法
- C++异常的处理关键字 try throw catch
- 可能出现异常的代码 放到 try块
- 利用throw抛出异常
- 利用catch捕获异常
- catch( 类型) 如果想捕获其他类型 catch(…)
- 如果捕获到的异常不想处理,而继续向上抛出,利用 throw
int myDivision(int a, int b)
{
if (b == 0)
{
//return -1;
//throw 1; //抛出int类型的异常
//throw 'a'; //抛出char类型的异常
//throw 3.14; //抛出double类型的异常
string str = "abc";
throw str;
}
return a / b;
}
void test01()
{
int a = 10;
int b = 0;
try
{
myDivision(a, b);
}
catch (int)
{
cout << &#34;int类型异常捕获&#34; << endl;
}
catch (char)
{
cout << &#34;char类型异常捕获&#34; << endl;
}
catch (double)
{
//异常必须有函数进行处理,如果没有任何处理,程序自动调用 terminate 函数,让程序中断
throw; //捕获到了异常,但是不想处理,继续向上抛出这个异常
cout << &#34;double类型异常捕获&#34; << endl;
}
catch (MyException e)
{
e.printError();
}
catch (...)
{
cout << &#34;其他类型异常捕获&#34; << endl;
}
}
int main() {
try
{
test01();
}
catch (double)
{
cout << &#34;double函数中 double类型异常捕获&#34; << endl;
}
catch (...)
{
cout << &#34;main函数中 其他类型异常捕获&#34; << endl;
}
system(&#34;pause&#34;);
return EXIT_SUCCESS;
}
- 异常必须有函数进行处理,如果都不去处理,程序自动调用 terminate函数,中断掉
- 异常可以是自定义数据类型
class MyException
{
public:
void printError()
{
cout << &#34;我自己的异常&#34; << endl;
}
};
int myDivision(int a, int b)
{
if (b == 0)
{
throw MyException(); //抛出 MyException的匿名对象
}
return a / b;
}
void test01()
{
int a = 10;
int b = 0;
try
{
myDivision(a, b);
}
catch (MyException e)
{
e.printError();
}
}
2、栈解旋
- 从try代码块开始,到throw抛出异常之前,所有栈上的数据都会被释放掉,
- 释放的顺序和创建顺序相反的,这个过程我们称为栈解旋
class MyException
{
public:
void printError()
{
cout << &#34;我自己的异常&#34; << endl;
}
};
class Person
{
public:
Person()
{
cout << &#34;Person的默认构造函数调用&#34; << endl;
}
~Person()
{
cout << &#34;Person的析构函数调用&#34; << endl;
}
};
int myDivision(int a, int b)
{
if (b == 0)
{
//从try代码块开始,到throw抛出异常之前,所有栈上的数据都会被释放掉,
//释放的顺序和创建顺序相反的,这个过程我们称为栈解旋
Person p1;
Person p2;
throw MyException(); //抛出 MyException的匿名对象
}
return a / b;
}
void test01()
{
int a = 10;
int b = 0;
try
{
myDivision(a, b);
}
catch (MyException e)
{
e.printError();
}
}

3、异常的接口声明
- 在函数中 如果限定抛出异常的类型,可以用异常的接口声明
- 语法: void func()throw(int ,double)
- throw(空)代表 不允许抛出异常
//异常接口申明 throw(空)代表不允许抛出异常
void func() throw(int, double)
{
throw 3.14;
}
4、异常变量的生命周期
- 抛出的是 throw MyException(); catch (MyException e) 调用拷贝构造函数 效率低
- 抛出的是 throw MyException(); catch (MyException &e) 只调用默认构造函数 效率高 推荐
- 抛出的是 throw &MyException(); catch (MyException *e) 对象会提前释放掉,不能在非法操作
- 抛出的是 new MyException(); catch (MyException *e) 只调用默认构造函数 自己要管理释放,次推荐
class MyException
{
public:
MyException()
{
cout << &#34;MyException默认构造函数调用&#34; << endl;
}
MyException(const MyException& e)
{
cout << &#34;MyException拷贝构造函数调用&#34; << endl;
}
~MyException()
{
cout << &#34;MyException析构函数调用&#34; << endl;
}
};
void doWork()
{
throw new MyException();
}
void test01()
{
try
{
doWork();
}
//抛出的是 throw MyException(); catch (MyException e) 调用拷贝构造函数 效率低
//抛出的是 throw MyException(); catch (MyException &e) 引用方式接收,只调用默认构造函数 效率高 推荐
//抛出的是 throw &MyException(); catch (MyException *e) 对象方式接收,对象会提前释放掉,不能在非法操作
//抛出的是 new MyException(); catch (MyException *e) 只调用默认构造函数 自己要管理释放
catch (MyException* e) //这里根据上面的可选不同方法,这里是最后一种
{
cout << &#34;自定义异常捕获&#34; << endl;
delete e;
}
}
5、异常的多态使用
- 提供基类异常类
- class BaseException
- 纯虚函数 virtual void printError() = 0;
- 子类空指针异常 和 越界异常 继承 BaseException
- 重写virtual void printError()
- 测试 利用父类引用指向子类对象
//异常的基类
class BaseException
{
public:
virtual void printError() = 0;
};
//空指针异常
class NULLPointerException :public BaseException
{
public:
virtual void printError()
{
cout << &#34;空指针异常&#34; << endl;
}
};
//越界异常
class OutOfRangeException :public BaseException
{
public:
virtual void printError()
{
cout << &#34;越界异常&#34; << endl;
}
};
void doWork()
{
//throw NULLPointerException();
throw OutOfRangeException();
}
void test01()
{
try
{
doWork();
}
catch (BaseException& e)
{
e.printError();
}
}
6、系统标准异常
标准库中也提供了很多的异常类,它们是通过类继承组织起来的。异常类继承层级结构图如下:明细的需要查表。

异常 | 描述 | std::exception | 该异常是所有标准 C++ 异常的父类。 | std::bad_alloc | 该异常可以通过 new 抛出。 | std::bad_cast | 该异常可以通过 dynamic_cast 抛出。 | std::bad_exception | 这在处理 C++ 程序中无法预期的异常时非常有用。 | std::bad_typeid | 该异常可以通过 typeid 抛出。 | std::logic_error | 理论上可以通过读取代码来检测到的异常。 | std::domain_error | 当使用了一个无效的数学域时,会抛出该异常。 | std::invalid_argument | 当使用了无效的参数时,会抛出该异常。 | std::length_error | 当创建了太长的 std::string 时,会抛出该异常。 | std::out_of_range | 该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator。 | std::runtime_error | 理论上不可以通过读取代码来检测到的异常。 | std::overflow_error | 当发生数学上溢时,会抛出该异常。 | std::range_error | 当尝试存储超出范围的值时,会抛出该异常。 | std::underflow_error | 当发生数学下溢时,会抛出该异常。 |
- 引入头文件 #include
- 抛出越界异常 throw out_of_range(“…”)
- 获取错误信息 catch( exception & e ) e.what();
class Person
{
public:
Person(int age)
{
if (age < 0 || age > 150)
{
throw out_of_range(&#34;年龄必须在 0 ~ 150之间&#34;);
//throw length_error(&#34;年龄必须在 0 ~ 150之间&#34;);
}
else
{
this->m_Age = age;
}
}
int m_Age;
};
void test01()
{
try
{
Person p(151);
}
//catch ( out_of_range &e) //这个是具体的异常,配合上面throw out_of_range
catch (exception& e) //这个是具体的异常的基类,可以输出任何异常
{
cout << e.what() << endl;
}
}
7、编写自己的异常类
- 编写myOutofRange 继承 Exception类
- 重写 virtual const char * what() const
- 将sting 转为 const char * .c_str()
- const char * 可以隐式类型转换为 string 反之不可以
- 测试,利用多态打印出错误提示信息
class MyOutOfRangeException :public exception // 这里继承了exception,下面的函数都是多态实现
{
public:
MyOutOfRangeException(const char* str)
{
//const char * 可以隐式类型转换为 string 反之不可以
this->m_errorInfo = str;
cout << &#34;const char* str 形式&#34; << endl;
}
MyOutOfRangeException(string str)
{
this->m_errorInfo = str;
cout << &#34;string str 形式&#34; << endl;
}
virtual const char* what() const
{
//将string 转为 const char *
return m_errorInfo.c_str();
}
string m_errorInfo;
};
class Person
{
public:
Person(int age)
{
if (age < 0 || age > 150)
{
//throw MyOutOfRangeException(string(&#34;年龄必须在0到150之间&#34;)); //MyOutOfRangeException(string str)
throw MyOutOfRangeException(&#34;年龄必须在0到150之间&#34;); //MyOutOfRangeException(const char* str)
}
else
{
this->m_Age = age;
}
}
int m_Age;
};
void test01()
{
try
{
Person p(1000);
}
catch (exception& e)
{
cout << e.what() << endl;
}
}

8、其他
个人觉得c++的异常的设计也有些问题,是历史遗留的,目前各个平台和编译器都支持的不完善。谷歌的编程规范里有详细的解释为什么不要用c++的异常。所以非必要时,尽量不要用异常(特指c++项目)
从C++17开始,不能再从函数写可能抛出某种异常。只能声明不能抛出某种异常noexcept,如果一个函数声明不会抛异常,结果抛出异常,C++运行时会调用std::terminate来终止应用程序。
个人感悟:学是为了掌握知识点,为了更好地不使用它。
相关链接
若觉得有用,欢迎 点赞/收藏 ~~~ |
|