1.C++ 异常概念
异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者处理这个错误。
- throw:当问题出现时,程序会抛出一个异常。这是通过使用
throw
关键字来完成的。 - catch:在您想要处理问题的地方,通过异常处理程序捕获异常。
catch
关键字用于捕获异常,可以有多个catch
进行捕获。 - try:
try
块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个catch
块。
如果有一个块抛出一个异常,捕获异常的方法会使用 try
和 catch
关键字。try
块中放置可能抛出异常的代码,try
块中的代码被称为保护代码。
2. 异常的抛出和捕获
异常的抛出和匹配原则
- 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个
catch
的处理代码。 - 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
- 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被
catch
以后销毁。(这里的处理类似于函数的传值返回) catch(...)
可以捕获任意类型的异常,问题是不知道异常错误是什么。- 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获,这个在实际中非常实用,我们后面会详细讲解这个。
在函数调用链中异常栈展开匹配原则
- 首先检查
throw
本身是否在try
块内部,如果是再查找匹配的catch
语句。如果有匹配的,则调到catch
的地方进行处理。 - 没有匹配的
catch
则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch
。 - 如果到达
main
函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的catch
子句的过程称为栈展开。所以实际中我们最后都要加一个catch(...)
捕获任意类型的异常,否则当有异常没捕获,程序就会直接终止。 - 找到匹配的
catch
子句并处理以后,会继续沿着catch
子句后面继续执行。
代码展示
void exe()
{throw 1;
}
void func()
{int x = 2, y = 0;auto ret = [=]()->int {if (y == 0) { throw " 除0错误"; } else { return x / y; }};std::cout << ret() << std::endl;
}
int main()
{try{func();exe();}catch (const char*tsr){cerr <<tsr << endl;}catch (...){cerr << "未知异常!" << endl;}return 0;
}
下面展示上面的代码进阶版也就是,当你捕获到异常进行紧急修复,然后重新抛出!重新抛出的异常会被调用链上层捕捉!切记这个调用链是给上走的!
void exe() {throw 1;
}void func() {int x = 2, y = 0;auto ret = [=]()->int {if (y == 0) {// 抛出标准异常类型,便于统一处理throw std::runtime_error("除0错误");} else {return x / y;}};try {std::cout << ret() << std::endl;} catch (const std::runtime_error& e) {std::cerr << e.what() << std::endl;// 修复除零错误,将除数修改为非零值y = 1;auto newRet = [x, y]()->int { return x / y; };std::cout << "修复后结果: " << newRet() << std::endl;// 重新抛出异常throw;}
}int main() {try {func();exe();} catch (const std::runtime_error& e) {std::cerr << "main函数捕获到重新抛出的异常: " << e.what() << std::endl;std::cout << "异常已在下层函数修复,程序继续执行" << std::endl;} catch (int e) {std::cerr << "捕获到异常: " << e << std::endl;// 这里可以添加对异常值为 1 的具体处理逻辑std::cout << "异常已处理,程序继续执行" << std::endl;} catch (...) {std::cerr << "未知异常!" << std::endl;}return 0;
}
3.继承抛异常
//
//在实际当中有一个抛出和捕获特例而且也非常常用!可以抛出派生类对象,让基类去捕获
#include <iostream>
#include <thread>
#include <chrono>
#include <cstdlib>
#include <ctime>
#include <string>// 假设存在基类 Exception,这里未给出完整定义,实际需包含相关头文件或完整实现
void SQLMgr();
//公司常用的异常继承体系!
class Exception
{
public:Exception(const std::string& errmsg, int id) : _errmsg(errmsg), _id(id) {}virtual std::string what() const = 0;
protected:std::string _errmsg;int _id;
};class HttpServerException : public Exception {
public:HttpServerException(const std::string& errmsg, int id, const std::string& type): Exception(errmsg, id), _type(type) {}virtual std::string what() const override {std::string str = "HttpServerException:";str += _type;str += ":";str += _errmsg;return str;}
private:const std::string _type;
};class CacheException : public Exception {
public:CacheException(const std::string& errmsg, int id): Exception(errmsg, id) {}virtual std::string what() const override {std::string str = "CacheException:";str += _errmsg;return str;}
};class SqlException : public Exception {
public:SqlException(const std::string& errmsg, int id, const std::string& sql): Exception(errmsg, id), _sql(sql) {}virtual std::string what() const override {std::string str = "SqlException:";str += _errmsg;str += "->";str += _sql;return str;}
private:const std::string _sql;
};void CacheMgr() {srand(time(0));if (rand() % 5 == 0) {throw CacheException("权限不足", 100);}else if (rand() % 6 == 0) {throw CacheException("数据不存在", 101);}SQLMgr();
}void HttpServer() {srand(time(0));if (rand() % 3 == 0) {throw HttpServerException("请求资源不存在", 100, "get");}else if (rand() % 4 == 0) {throw HttpServerException("权限不足", 101, "post");}CacheMgr();
}void SQLMgr() {srand(time(0));if (rand() % 7 == 0) {throw SqlException("权限不足", 100, "select * from name = '张三'");}//throw "xxxxxx";
}int main() {while (1) {std::this_thread::sleep_for(std::chrono::seconds(1));try {HttpServer();}catch (const Exception& e) //捕获父类对象{std::cout << e.what() << std::endl;}catch (...) {std::cout << "Unkown Exception" << std::endl;}}return 0;
}