Skip to main content

第35章 异常处理

在程序运行过程中,难免会出现各种错误(如除以零、数组越界、内存分配失败等)。C++ 提供了异常处理机制,用于在程序出错时捕获错误并进行相应处理,避免程序直接崩溃,提高程序的健壮性和容错能力。

35.1 异常处理的基本概念

异常是程序运行时出现的非正常情况(运行错误)。异常处理核心机制:抛出throw、捕获catch、监测代码块try。 完整流程:try包裹风险代码,出错时执行throw抛出异常;程序跳转至匹配的catch块处理;若无匹配捕获,程序终止。

35.2 异常处理基础语法

try {
// 可能出错的代码
if(错误条件成立){
throw 异常数据;
}
}
catch(异常类型1 变量) {
// 处理类型1异常
}
catch(异常类型2 变量) {
// 处理类型2异常
}
// 可使用catch(...)捕获所有异常

35.2.1 try块

必须紧跟至少一个catch,仅包裹存在运行风险的代码,作用是开启异常监听。

35.2.2 throw语句

主动抛出异常,可抛出任意基础类型、结构体、类对象: throw 表达式; 抛出的数据会复制一份传递给对应catch

35.3 基础异常示例(数值错误码)

#include <iostream>
#include <cmath>
using namespace std;

// 计算三角形面积,边长非法抛出错误码
double triangleArea(int a, int b, int c) {
if(a <= 0 || b <=0 || c <=0) {
throw -1; // 边长负数,抛出-1
}
if(a + b <= c || a + c <= b || b + c <= a) {
throw -2; // 无法构成三角形
}
double s = (a + b + 1.0) / 2;
return sqrt(s*(s-a)*(s-b)*(s-c));
}

int main() {
try {
double area = triangleArea(3,4,5);
cout << "三角形面积:" << area << endl;
area = triangleArea(1,1,3); // 触发异常
cout << "三角形面积:" << area << endl;
}catch (int errCode) {
if(errCode == -1) {
cout << "错误:边长必须为正数" << endl;
}else if(errCode == -2) {
cout << "错误:给定边长无法构成三角形" << endl;
}
}
return 0;
}

35.4 自定义异常类

基础类型传递信息有限,自定义类可携带错误代码、错误描述。

#include <iostream>
#include <string>
using namespace std;

class MyException {
private:
int code;
string msg;
public:
MyException(int c, string m): code(c), msg(m) {}
int getCode() const { return code; }
string getMsg() const { return msg; }
};

void openFile(string filename) {
if(filename.empty()) {
throw MyException(1001, "文件名不能为空");
}
if(filename.find(".txt") == string::npos) {
throw MyException(1002, "文件必须是.txt格式");
}
cout << "文件" << filename << "打开成功" << endl;
}

int main() {
try {
openFile("data.doc");
}catch(const MyException& e) {
cout << "错误代码:" << e.getCode() << endl;
cout << "错误信息:" << e.getMsg() << endl;
}
return 0;
}

35.5 异常的传播机制

若当前try-catch没有匹配的异常类型,异常会向上传递到上层调用函数,逐层查找捕获;全程无捕获则程序调用terminate直接结束。 示例:

#include <iostream>
using namespace std;

void func3() {
cout << "进入func3" << endl;
throw "func3中发生异常";
cout << "离开func3" << endl; // 不会执行
}
void func2() {
cout << "进入func2" << endl;
func3();
cout << "离开func2" << endl; // 不会执行
}
void func1() {
cout << "进入func1" << endl;
try {
func2();
}catch(const char* msg) {
cout << "在func1中捕获异常:" << msg << endl;
}
cout << "离开func1" << endl;
}

int main() {
func1();
return 0;
}

输出:

进入func1
进入func2
进入func3
在func1中捕获异常:func3中发生异常
离开func1

35.6 异常处理规则

  1. 类型严格匹配catch捕获类型必须与抛出类型一致;派生类异常可被基类引用捕获。
class Base {};
class Derived : public Base {};
int main() {
try {
throw Derived();
}catch(Base& b) {
cout << "捕获派生类异常" << endl;
}
return 0;
}
  1. 万能捕获catch(...):放在所有catch最后,捕获任意类型未知异常。
  2. 禁止析构函数抛异常:析构抛出异常无捕获会导致资源泄漏、程序崩溃。
  3. 构造函数可抛异常:构造抛出异常时,对象不会完整创建,已分配资源需手动释放。
  4. 抛出的异常对象会复制,catch建议使用引用接收,减少拷贝开销。

35.7 C++标准异常库

头文件<stdexcept>,所有标准异常继承std::exception,通用what()方法返回错误字符串:

异常类触发场景
std::out_of_range数组/容器下标越界(vector.at)
std::invalid_argument传入非法参数
std::bad_allocnew内存分配失败

示例:

#include <iostream>
#include <vector>
#include <stdexcept>
using namespace std;

int main() {
try {
vector<int> v(5);
v.at(10) = 1; // 越界抛出out_of_range
}catch(const out_of_range& e) {
cout << "越界错误:" << e.what() << endl;
}
try {
int* p = new int[999999999999]; // 内存不足抛bad_alloc
}catch(const bad_alloc& e) {
cout << "内存分配失败:" << e.what() << endl;
}
return 0;
}

35.8 异常vs错误码对比

  1. 错误码:函数返回标识,正常业务代码与错误判断大量混杂,代码冗余;适合轻微可预期错误。
  2. 异常:正常逻辑与错误处理分离,错误自动向上传递;适合不可预测严重运行错误。