1. 异常的概念
异常是程序在运行期间产生的问题,而非编译期间产生的问题,因此程序的语法没有问题,但是逻辑出现了问题。
异常本质上提供的是一种程序控制权转移的方式,程序一旦出现异常没有经过正确的处理,就会造成程序运行的崩溃。
处理异常的方式有两种:
- 抛出异常 throw
- 捕获异常 try-catch
2. throw 抛出异常
throw语句抛出的是通常是一个对象,对象的类型就是异常的类型,throw可以把这个对象抛出到调用的上一级。如果上一级没有正确的处理异常,异常对象会逐级向上反馈,如果在主函数中仍然没有正确处理异常,程序运行崩溃。
#include <iostream>
using namespace std;
/**
* @brief divide 除法
* @param a 被除数
* @param b 除数
* @return 商
*/
double divide(double a,double b)
{
if(b == 0)
throw "除数为0!!!";
return a/b;
}
int main()
{
cout << divide(4,5) << endl;
cout << divide(6,7) << endl;
cout << divide(1,0) << endl; // 运行终止
cout << "主函数结束" << endl;
return 0;
}
3. try-catch 捕获异常
在try块中放置可能抛出异常的代码,在catch代码中对异常类型进行匹配:
- 如果匹配成功,程序从try块抛出异常的位置直接跳转到catch块,执行弥补错误的代码。执行完catch块后,try-catch结束。
#include <iostream>
using namespace std;
/**
* @brief divide 除法
* @param a 被除数
* @param b 除数
* @return 商
*/
double divide(double a,double b)
{
if(b == 0)
throw "除数为0!!!";
return a/b;
}
int main()
{
cout << divide(4,5) << endl;
try
{
cout << divide(1,0) << endl;
cout << divide(6,7) << endl; // 没有执行的机会
}catch(const char* e)
{
cout << e << endl;
// 弥补的措施
}
cout << "主函数结束" << endl;
return 0;
}
- 如果匹配失败,异常会交给上一级处理,直到主函数。
#include <iostream>
using namespace std;
/**
* @brief divide 除法
* @param a 被除数
* @param b 除数
* @return 商
*/
double divide(double a,double b)
{
if(b == 0)
throw "除数为0!!!";
return a/b;
}
void test()
{
try
{
cout << divide(1,0) << endl;
cout << divide(6,7) << endl; // 没有执行的机会
}catch(string e) // 匹配失败
{
cout << e << endl;
// 弥补的措施
}
}
int main()
{
cout << divide(4,5) << endl;
try
{
test();
}catch(const char* e) // 最后一次机会捕获成功
{
cout << e << endl;
// 弥补措施
}
cout << "主函数结束" << endl;
return 0;
}
4. 标准异常
C++为常见的异常类型进行了分类,如下所示。
使用上面的异常类型需要引入头文件 #include <stdexcept>
程序员自定义异常最好通过继承加入到上面的标准异常家族中,方便后续处理和管理。
#include <iostream>
#include <stdexcept> // 头文件
using namespace std;
// 自定义异常类型
class ZeroException:public exception
{
public:
// throw() 是异常规格说明,表示此函数不会抛出异常
const char* what() const throw()
{
return "除数为0!!!";
}
};
/**
* @brief divide 除法
* @param a 被除数
* @param b 除数
* @return 商
*/
double divide(double a,double b)
{
if(b == 0)
throw ZeroException();
return a/b;
}
int main()
{
cout << divide(4,5) << endl;
try
{
cout << divide(1,0) << endl;
cout << divide(6,7) << endl; // 没有执行的机会
}catch(const ZeroException& e)
{
cout << e.what() << endl;
// 弥补的措施
}
cout << "主函数结束" << endl;
return 0;
}
5. 捕获基类异常
可以捕获基类异常,同时匹配所有派生类异常类型。
#include <iostream>
#include <stdexcept> // 头文件
using namespace std;
class Animal
{
public:
string a = "Animal";
virtual void eat()
{
cout << "吃东西" << endl;
}
};
class Monkey:public Animal
{
public:
string b = "Monkey";
virtual void eat()
{
cout << "吃香蕉" << endl;
}
};
int main()
{
string s = "fsdjkfgsdhj";
int err_id;
cin >> err_id;
try
{
if(err_id%3 == 0) // 可以捕获
cout << s.at(348568734) << endl;
else if(err_id%3 == 2) // 可以捕获
throw length_error("长度错误");
else // 不能捕获
{
Animal a1;
Monkey& m2 = dynamic_cast<Monkey&>(a1);
}
}catch(logic_error& e)
{
cout << e.what() << endl;
cout << "弥补措施" << endl;
}
cout << "主函数结束" << endl;
return 0;
}
6. 多重捕获
在上面的代码中,虽然可以同时处理多种异常类型,但是是牺牲捕获精度实现的,为了保证捕获的精度,可以使用多重捕获。
在使用多重捕获时,一定要先catch派生类异常类型,再catch基类异常类型。
#include <iostream>
#include <stdexcept> // 头文件
using namespace std;
class Animal
{
public:
string a = "Animal";
virtual void eat()
{
cout << "吃东西" << endl;
}
};
class Monkey:public Animal
{
public:
string b = "Monkey";
virtual void eat()
{
cout << "吃香蕉" << endl;
}
};
int main()
{
string s = "fsdjkfgsdhj";
int err_id;
cin >> err_id;
try
{
if(err_id%3 == 0) // 可以捕获
cout << s.at(348568734) << endl;
else if(err_id%3 == 2) // 可以捕获
throw length_error("长度错误");
else // 不能捕获
{
Animal a1;
Monkey& m2 = dynamic_cast<Monkey&>(a1);
}
}
catch(out_of_range& e)
{
cout << e.what() << endl;
cout << "分支1" << endl;
}
catch(length_error& e)
{
cout << e.what() << endl;
cout << "分支2" << endl;
}
catch(logic_error& e)
{
cout << e.what() << endl;
cout << "保底1" << endl;
}
catch(exception& e)
{
cout << e.what() << endl;
cout << "保底2" << endl;
}
cout << "主函数结束" << endl;
return 0;
}
C++的异常处理机制并不完善,是否使用取决于所在的开发团队和技术框架。