C++ 异常处理机制与类型转换详解
1. 异常的概念及使用
1.1 异常的概念
异常 (Exception) 是程序运行时发生的、打断正常指令执行流程的意外错误或异常事件,是程序与开发者之间用于传递运行故障的标准化机制。
C++ 异常处理机制通过 throw、try、catch 关键字实现运行时错误的检测与分离,支持栈展开和重新抛出,需关注资源泄漏问题。类型转换涵盖隐式转换及 C++ 四种显式转换运算符(static_cast、dynamic_cast、const_cast、reinterpret_cast),确保类型安全。RTTI 提供运行时类型识别能力,但存在性能开销。本文详解异常规范、标准库异常体系及类型转换最佳实践。

异常 (Exception) 是程序运行时发生的、打断正常指令执行流程的意外错误或异常事件,是程序与开发者之间用于传递运行故障的标准化机制。
核心界定:什么是异常? 异常特指程序运行阶段出现的错误,并非代码语法错误:
C++ 中常见的异常场景:
new 申请内存不足);本质:错误处理的现代化方案
在没有异常机制时,C/C++ 传统的错误处理依赖返回值/错误码(如函数返回 -1、0 表示失败),这种方式存在致命缺陷:
C++ 异常机制的核心思想: 将错误的检测与错误的处理彻底分离:
异常机制的三大核心关键字: C++ 用三个关键字实现完整的异常流程,是异常概念的核心载体:
throw:抛出异常,主动上报运行时错误;try:包裹可能触发异常的代码块,标记需要监控错误的区域;catch:捕获并处理对应类型的异常,是错误的兜底方案。一句话总结: 异常是 C++ 用于处理运行时错误的专用机制,通过抛出 - 捕获模型分离错误检测与处理逻辑,让程序在遭遇意外时不崩溃、可恢复,是编写健壮、安全的工业级代码的基础能力。
throw) 一个对象来引发一个异常,该对象的类型以及当前的调用链决定了应该由哪个 catch 的处理代码来处理该异常。throw 执行时,throw 后面的语句将不再被执行。程序的执行从 throw 位置跳到与之匹配的 catch 模块,catch 可能是同一函数中的一个局部的 catch,也可能是调用链中另一个函数中的 catch,控制权从 throw 位置转移到了 catch 位置。这里还有两个重要的含义:
catch 子句后销毁。(这里的处理类似于函数的传值返回)。catch 子句,首先检查 throw 本身是否在 try 块内部,如果在则查找匹配的 catch 语句,如果有匹配的,则跳到 catch 的地方进行处理。try/catch 子句,或者有 try/catch 子句但是类型不匹配,则退出当前函数,继续在外层调用函数链中查找,上述查找的 catch 过程被称为栈展开。main 函数,依旧没有找到匹配的 catch 子句,程序会调用标准库的 terminate 函数终止程序。catch 子句处理后,catch 子句代码会继续执行。#include <iostream>
using namespace std;
double Divide(int a, int b) {
try {
// 当 b == 0 时抛出异常
if (b == 0) {
string s("Divide by zero condition!");
throw s;
} else {
return ((double)a / (double)b);
}
} catch (int errid) {
cout << errid << endl;
}
return 0;
}
void Func() {
int len, time;
cin >> len >> time;
try {
cout << Divide(len, time) << endl;
} catch (const char* errmsg) {
cout << errmsg << endl;
}
cout << __FUNCTION__ << ":" << __LINE__ << "行执行" << endl;
}
int main() {
while (1) {
try {
Func();
} catch (const string& errmsg) {
cout << errmsg << endl;
}
}
return 0;
}
catch 是类型完全匹配的,如果有多个类型匹配的,就选择离它位置更近的那个。main 函数,异常仍旧没有被匹配就会终止程序,不是发生严重错误的情况下,我们是不期望程序终止的,所以一般 main 函数中最后都会使用 catch(...),它可以捕获任意类型的异常,但是不知道异常错误是什么。#define _CRT_SECURE_NO_WARNINGS 1
#include <thread> // 用于 this_thread::sleep_for 线程休眠
#include <iostream>
#include <string>
using namespace std;
// 一般大型项目程序才会使用异常,下面我们模拟设计一个服务的几个模块
// 每个模块的继承都是 Exception 的派生类,每个模块可以添加自己的数据
// 最后捕获时,我们捕获基类就可以
class Exception {
public:
// 构造函数:错误信息 + 错误编号
Exception(const string& errmsg, int id) : _errmsg(errmsg), _id(id) {}
// 虚函数:多态的核心!返回错误描述
virtual string what() const {
return _errmsg;
}
int getid() const {
return _id;
}
protected:
string _errmsg; // 错误信息
int _id; // 错误码
};
// 数据库异常:额外存储执行失败的 SQL 语句
class SqlException : public Exception {
public:
SqlException(const string& errmsg, int id, const string& sql) : Exception(errmsg, id), _sql(sql) {}
// 重写虚函数
virtual string what() const {
string str = "SqlException:";
str += _errmsg;
str += "->";
str += _sql;
return str;
}
private:
const string _sql;
};
// 缓存异常
class CacheException : public Exception {
public:
CacheException(const string& errmsg, int id) : Exception(errmsg, id) {}
virtual string what() const {
string str = "CacheException:";
str += _errmsg;
return str;
}
};
// HTTP 请求异常:额外存储请求类型 (get/post)
class HttpException : public Exception {
public:
HttpException(const string& errmsg, int id, const string& type) : Exception(errmsg, id), _type(type) {}
virtual string what() const override {
string str = "HttpException:";
str += _type;
str += ":";
str += _errmsg;
return str;
}
private:
const string _type;
};
// 最底层:数据库模块
void SQLMgr() {
if (rand() % 7 == 0) {
// 抛派生类异常
throw SqlException("权限不足", 100, "select * from name = '张三'");
} else {
cout << "SQLMgr 调用成功" << endl;
}
}
// 中间层:缓存模块 → 调用数据库
void CacheMgr() {
if (rand() % 5 == 0) {
throw CacheException("权限不足", 100);
} else if (rand() % 6 == 0) {
throw CacheException("数据不存在", 101);
} else {
cout << "CacheMgr 调用成功" << endl;
}
SQLMgr();
}
// 最上层:HTTP 服务 → 调用缓存
void HttpServer() {
if (rand() % 3 == 0) {
throw HttpException("请求资源不存在", 100, "get");
} else if (rand() % 4 == 0) {
throw HttpException("权限不足", 101, "post");
} else {
cout << "HttpServer 调用成功" << endl;
}
CacheMgr();
}
int main() {
srand(time(0)); // 初始化随机数种子
while (1) // 死循环,模拟服务持续运行
{
this_thread::sleep_for(chrono::seconds(1)); // 每秒执行一次
try {
HttpServer(); // 执行顶层业务
} catch (const Exception& e) // 这里捕获基类,基类对象和派生类对象都可以被捕获
{
cout << e.what() << endl; // 多态调用:执行派生类重写的 what()
}
// 兜底捕获:捕获所有未知类型异常
catch (...) {
cout << "Unkown Exception" << endl;
}
}
return 0;
}
代码整体功能概述 模拟后端服务三层架构:HttpServer(网络层) → CacheMgr(缓存层) → SQLMgr(数据库层); 自定义异常继承体系:基类 Exception,派生数据库、缓存、HTTP 三种专用异常; 随机触发异常:模拟业务运行时的随机错误; 多态捕获异常:在主函数仅捕获基类异常引用,即可统一处理所有派生类异常; 死循环 + 每秒执行:持续模拟服务运行。
核心:自定义异常继承体系 1️⃣这是大型项目异常设计的标准规范:基类抽象异常 + 派生类细分业务异常。 (1) 异常基类 Exception ✅ 设计要点:
what() 定义为 virtual 虚函数:为了多态调用 (捕获基类时,能执行派生类重写的逻辑);what() 定制专属错误信息,并扩展独有成员。2️⃣业务模块函数:分层调用 + 抛异常
代码模拟了三层服务调用链:
main() → HttpServer() → CacheMgr() → SQLMgr()
函数内部用 rand() 随机触发异常,throw 抛出派生类异常对象。
✅ 异常机制关键行为:
一旦执行 throw,当前函数立即终止,程序会栈展开,直接跳转到最近的 catch 块,中间所有未执行完的代码都不会运行。
有时 catch 到一个异常对象后,需要对错误进行分类,其中的某种异常错误需要进行特殊的处理,其他错误则重新抛出异常给外层调用链处理。捕获异常后需要重新抛出,直接 throw;就可以把捕获的对象直接抛出。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
using namespace std;
// 下面程序模拟展示了聊天时发送消息,发送失败补货异常,但是可能在
// 电梯地下室等场景手机信号不好,则需要多次尝试,如果多次尝试都发
// 送不出去,则就需要捕获异常再重新抛出,其次如果不是网络差导致的
// 错误,捕获后也要重新抛出。
class Exception {
public:
Exception(const string& errmsg, int id) : _errmsg(errmsg), _id(id) {}
// 虚函数:多态获取错误信息
virtual string what() const {
return _errmsg;
}
// 获取错误码
int getid() const {
return _id;
}
protected:
string _errmsg; // 错误描述
int _id; // 错误码
};
// HTTP 异常类(继承自 Exception)
class HttpException : public Exception {
public:
HttpException(const string& errmsg, int id, const string& type) : Exception(errmsg, id), _type(type) {}
// 重写虚函数,定制 HTTP 错误信息格式
virtual string what() const override {
string str = "HttpException:";
str += _type;
str += ":";
str += _errmsg;
return str;
}
private:
const string _type;
};
void _SeedMsg(const string& s) {
if (rand() % 2 == 0) {
throw HttpException("网络不稳定,发送失败", 102, "put");
} else if (rand() % 7 == 0) {
throw HttpException("你已经不是对象的好友,发送失败", 103, "put");
} else {
cout << "发送成功" << endl;
}
}
void SendMsg(const string& s) {
// 发送消息失败,则再重试 3 次
for (size_t i = 0; i < 4; i++) {
try {
_SeedMsg(s);
break;
} catch (const Exception& e) {
// 捕获异常,if 中是 102 号错误,网络不稳定,则重新发送
// 捕获异常,else 中不是 102 号错误,则将异常重新抛出
if (e.getid() == 102) {
// 重试三次以后否失败了,则说明网络太差了,重新抛出异常
if (i == 3) throw;
cout << "开始第" << i + 1 << "重试" << endl;
} else {
throw;
}
}
}
}
int main() {
srand(time(0));
string str;
while (cin >> str) {
try {
SendMsg(str);
} catch (const Exception& e) {
cout << e.what() << endl << endl;
} catch (...) {
cout << "Unkown Exception" << endl;
}
}
return 0;
}
#include <iostream>
using namespace std;
double Divide(int a, int b) {
// 当 b == 0 时抛出异常
if (b == 0) {
throw "Division by zero condition!";
}
return (double)a / (double)b;
}
void Func() {
// 这里可以看到如果发生除 0 错误抛出异常,另外下面的 array 没有得到释放。
// 所以这里捕获异常后并不处理异常,异常还是交给外层处理,这里捕获了再
// 重新抛出去。
int* array = new int[10];
try {
int len, time;
cin >> len >> time;
cout << Divide(len, time) << endl;
} catch (...) {
// 捕获异常释放内存
cout << "delete []" << array << endl;
delete[] array;
throw; // 异常重新抛出,捕获到什么抛出什么
}
cout << "delete []" << array << endl;
delete[] array;
}
int main() {
try {
Func();
} catch (const char* errmsg) {
cout << errmsg << endl;
} catch (const exception& e) {
cout << e.what() << endl;
} catch (...) {
cout << "Unkown Exception" << endl;
}
return 0;
}
throw(),表示函数不抛异常,函数参数列表的后面接 throw(类型 1,类型 2…) 表示可能会抛出多种类型的异常,可能会抛出的类型用逗号分割。noexcept 表示不会抛出异常,啥都不加表示可能会抛出异常。noexcept,也就是说如果一个函数用 noexcept 修饰了,但是同时又包含了 throw 语句或者调用的函数可能会抛出异常,编译器还是会顺利编译通过的 (有些编译器可能会报个警告)。但是一个声明了 noexcept 的函数抛出了异常,程序会调用 terminate 终止程序。noexcept(expression) 还可以作为一个运算符去检测一个表达式是否会抛出异常,可能会则返回 false,不会就返回 true。#include <iostream>
using namespace std;
// C++98
// 这里表示这个函数只会抛出 bad_alloc 的异常
// void* operator new (std::size_t size) throw (std::bad_alloc);
// 这里表示这个函数不会抛出异常
// void* operator delete (std::size_t size, void* ptr) throw();
// C++11
// size_type size() const noexcept;
// iterator begin() noexcept;
// const_iterator begin() const noexcept;
class MyContainer {
private:
int* _data;
size_t _size;
public:
// 构造函数
MyContainer(size_t size) : _size(size) { _data = new int[size](); } // 初始化内存
// 析构函数
~MyContainer() { delete[] _data; }
size_t size() const noexcept { return _size; }
int* begin() noexcept { return _data; }
const int* begin() const noexcept { return _data; }
};
double Divide(int a, int b) {
// 当 b == 0 时抛出异常
if (b == 0) {
throw "Division by zero condition!";
}
return (double)a / (double)b;
}
int main() {
try {
int len, time;
cin >> len >> time;
cout << Divide(len, time) << endl;
} catch (const char* errmsg) {
cout << "捕获异常:" << errmsg << endl;
} catch (...) {
cout << "Unkown Exception" << endl;
}
int i = 0;
cout << "noexcept(Divide(1,2))= " << noexcept(Divide(1, 2)) << "(1=不抛,0=抛)" << endl;
cout << "noexcept(Divide(1,0))=" << noexcept(Divide(1, 0)) << "(1=不抛,0=抛)" << endl;
cout << "noexcept(++i)=" << noexcept(++i) << "(1=不抛,0=抛)" << endl;
MyContainer container(5);
cout << "容器大小 size = " << container.size() << endl;
return 0;
}
这张图是 C++ 标准库中 std::exception 类的官方文档,它是整个 C++ 异常体系的根基类。我这里只说明一下基本的情况,想了解详细的内容可以自己点击链接进去看看。
1️⃣整体定位
头文件:<exception>
身份:std::exception 是所有 C++ 标准库异常的公共基类。
核心价值:标准库中所有组件抛出的异常 (如内存分配失败 std::bad_alloc、越界访问 std::out_of_range 等) 都继承自它。因此,只要捕获 const std::exception&,就能统一捕获所有标准库异常,实现错误的集中处理。
2️⃣C++98 版本的类声明解析
⚠️注意:throw() 是 C++98 的动态异常规范,用来声明函数可能抛出的异常类型;throw() 空括号表示不会抛出任何异常。C++11 之后,这种写法被 noexcept 关键字替代,但功能完全等价。
3️⃣设计思想与使用场景
多态捕获:因为 what() 是虚函数,当你捕获基类 std::exception 引用时,调用 e.what() 会自动绑定到实际派生类的实现,从而拿到具体的错误描述。
自定义异常的基类:实际项目中,我们也会继承 std::exception 来实现自定义异常类,这样就能和标准库异常体系兼容,被统一捕获。
4️⃣C++11 之后的演进
C++11 引入 noexcept 后,std::exception 的声明被更新为:
把 throw() 替换为 noexcept,语义完全一致,但语法更简洁,是现代 C++ 的推荐写法。
核心接口 what() 保持不变,保证了向后兼容。
C++ 标准库也定义了一套自己的一套异常继承体系库,基类是 exception,所以我们日常写程序,需要在主函数捕获 exception 即可,要获取异常信息,调用 what 函数,what 是一个虚函数,派生类可以重写。
#include <iostream>
using namespace std;
int main() {
int i = 1;
// 隐式类型转换
// 隐式类型转换主要发生在整形和整形之间,整形和浮点数之间,浮点数和浮点数之间
double d = i;
printf("%d, %.2f\n", i, d);
int* p = &i;
// 显示的强制类型转换
// 强制类型转换主要发生在指针和整形之间,指针和指针之间
int address = (int)p;
printf("%p, %d\n", p, address);
// malloc 返回值是 void*,被强转成 int*
int* ptr = (int*)malloc(8);
// 编译报错:类型强制转换:无法从'int *'转换为'double'
// 指针是地址的编号,也是一种整数,所以可以和整形互相转换
// 但是指针和浮点数毫无关联,强转也是不支持的
// d = (double)p;
return 0;
}
operator 类型 () 的函数去支持。#include <iostream>
using namespace std;
// 内置类型和自定义类型之间
// 1、自定义类型 = 内置类型 -> 构造函数支持
// 2、内置类型 = 自定义类型 -> operator 内置类型 支持
class A {
public:
// 构造函数加上 explicit 就不支持隐式类型转换了
// explicit A(int a)
A(int a) : _a1(a), _a2(a) {}
A(int a1, int a2) : _a1(a1), _a2(a2) {}
// 加上 explicit 就不支持隐式类型转换了
// explicit operator int()
operator int() const {
return _a1 + _a2;
}
private:
int _a1 = 1;
int _a2 = 1;
};
class B {
public:
B(int b) : _b1(b) {}
// 支持 A 类型对象转换为 B 类型对象
B(const A& aa) : _b1(aa) {}
private:
int _b1 = 1;
};
int main() {
// 单参数的转换
string s1 = "1111111";
A aa1 = 1;
A aa2 = (A)1;
// 多参数的转换
A aa3 = {2, 2};
const A& aa4 = {2, 2};
int z = aa1.operator int();
int x = aa1;
int y = (int)aa2;
cout << x << endl;
cout << y << endl;
cout << z << endl;
std::shared_ptr<int> foo;
std::shared_ptr<int> bar(new int(34));
// if(foo.operator bool())
if (foo) std::cout << "foo points to " << *foo << '\n';
else std::cout << "foo is null\n";
if (bar) std::cout << "bar points to " << *bar << '\n';
else std::cout << "bar is null\n";
// A 类型对象隐式转换为 B 类型
B bb1 = aa1;
B bb2(2);
bb2 = aa1;
const B& ref1 = aa1;
return 0;
}
// 自定义类型之间转换的实践样例
#include <iostream>
#include <assert.h>
using namespace std;
namespace lcz {
template <class T>
struct ListNode {
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
ListNode(const T& data = T()) : _next(nullptr), _prev(nullptr), _data(data) {}
};
template <class T, class Ref, class Ptr>
struct ListIterator {
typedef ListNode<T> Node;
typedef ListIterator<T, Ref, Ptr> Self;
Node* _node;
ListIterator(Node* node) : _node(node) {}
// typedef ListIterator<T, T&, T*> iterator;
// typedef ListIterator<T, const T&, const T*> const_iterator;
// ListIterator 实例化为 iterator 时,这个函数是拷贝构造
// ListIterator 实例化为 const_iterator 时,这个函数支持 iterator 转换为 const_iterator 构造函数
ListIterator(const ListIterator<T, T&, T*>& it) : _node(it._node) {}
// ++it;
Self& operator++() {
_node = _node->_next;
return *this;
}
Self& operator--() {
_node = _node->_prev;
return *this;
}
Self operator++(int) {
Self tmp(*this);
_node = _node->_next;
return tmp;
}
Self& operator--(int) {
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
Ref operator*() {
return _node->_data;
}
Ptr operator->() {
return &_node->_data;
}
bool operator!=(const Self& it) {
return _node != it._node;
}
bool operator==(const Self& it) {
return _node == it._node;
}
};
template <class T>
class list {
typedef ListNode<T> Node;
public:
// 同一个类模板给不同参数会实例化出不同的类型
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
iterator begin() {
return iterator(_head->_next);
}
const_iterator begin() const {
return const_iterator(_head->_next);
}
iterator end() {
return iterator(_head);
}
const_iterator end() const {
return const_iterator(_head);
}
void empty_init() {
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
}
list() {
empty_init();
}
list(initializer_list<T> il) {
empty_init();
for (const auto& e : il) {
push_back(e);
}
}
void push_back(const T& x) {
insert(end(), x);
}
// 没有 iterator 失效
iterator insert(iterator pos, const T& x) {
Node* cur = pos._node;
Node* newnode = new Node(x);
Node* prev = cur->_prev;
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
private:
Node* _head;
};
}
int main() {
lcz::list<int> lt = {1, 2, 3, 4};
// 权限缩小?权限缩小和放大,仅限于 const 的指针和引用
// 这里不是权限缩小,这里是自定义类型=自定义类型之间的类型转换
// 具体实现看下面 ListIterator 中对应的构造函数的实现
lcz::list<int>::const_iterator cit = lt.begin();
while (cit != lt.end()) {
cout << *cit << " ";
++cit;
}
cout << endl;
return 0;
}
int* 的指针强转成 double* 访问就会出现越界。static_cast/reinterpret_cast/const_cast/dynamic_cast 就是为了让类型转换相对而言更安全。#include <iostream>
using namespace std;
void insert(size_t pos, char ch) {
// 这里当 pos==0 时,就会引发由于隐式类型转换
// end 跟 pos 比较时,提升为 size_t 导致判断结束逻辑出现问题
// 在数组中访问挪动数据就会出现越界,经典的类型安全问题
int end = 10;
while (end >= pos) {
// ...
cout << end << endl;
--end;
}
}
int main() {
insert(5, 'x');
// insert(0, 'x');
// 这里会本质已经出现了越界访问,只是越界不一定能被检查出来
int x = 100;
double* p1 = (double*)&x;
cout << *p1 << endl;
const int y = 0;
// volatile const int y = 0;
// 加上 volatile 之后,y 的输出结果就是 1
int* p2 = (int*)&y;
(*p2) = 1;
// 这里打印的结果是 1 和 0,也是因为我们类型转换去掉了 const 属性
// 但是编译器认为 y 是 const 的,不会被改变,所以会优化编译时放到
// 寄存器或者直接替换 y 为 0 导致的
cout << *p2 << endl;
cout << y << endl;
return 0;
}
static_cast 用于两个类型意义相近的转换,这个转换是具有明确定义的,只要底层不包含 const,都可以使用 static_cast。reinterpret_cast 用于两个类型意义不相近的转换,reinterpret 是重新解释的意思,通常为运算对象的位模式提供较低层次上的重新解释,也就是说转换后对原有内存的访问解释已经完全改变了,非常的大胆。所以我们要谨慎使用,清楚知道这样转换是没有内存访问安全问题的。const_cast 用于 const 类型到非 const 类型的转换,去掉了 const 属性,也是一样的我们要谨慎使用,否则可能会出现意想不到的结果。dynamic_cast 用于将基类的指针或者引用安全的转换成派生类的指针或者引用。如果基类的指针或者引用时指向派生类对象的,则转换回派生类指针或者引用时可以成功的,如果基类的指针指向基类对象,则转换失败返回 nullptr,如果基类引用指向基类对象,则转换失败,抛出 bad_cast 异常。dynamic_cast 要求基类必须是多态类型,也就是基类中必须有虚函数。因为 dynamic_cast 是运行时通过虚表中存储的 type_info 判断基类指针指向的是基类对象还是派生类对象。#include <iostream>
using namespace std;
int main() {
// 对应隐式类型转换 -- 数据的解释意义没有改变
double d = 12.34;
int a = static_cast<int>(d);
cout << a << endl;
int&& ref = static_cast<int&&>(a);
// 对应强制类型转换 -- 数据的解释意义已经发生改变
int* p1 = reinterpret_cast<int*>(a);
// 对应强制类型转换中有风险的去掉 const 属性
// 所以要注意加 volatile
volatile const int b = 0;
int* p2 = const_cast<int*>(&b);
*p2 = 1;
cout << b << endl;
cout << *p2 << endl;
return 0;
}
#include <iostream>
using namespace std;
class A {
public:
virtual void f() {}
int _a = 1;
};
class B : public A {
public:
int _b = 2;
};
void fun1(A* pa) {
// 指向父类转换时有风险的,后续访问存在越界访问的风险
// 指向子类转换时安全
B* pb1 = (B*)pa;
cout << "pb1:" << pb1 << endl;
cout << pb1->_a << endl;
cout << pb1->_b << endl;
pb1->_a++;
pb1->_b++;
cout << pb1->_a << endl;
cout << pb1->_b << endl;
}
void fun2(A* pa) {
// dynamic_cast 会先检查是否能转换成功 (指向子类对象),能成功则转换,
// (指向父类对象) 转换失败则返回 nullptr
B* pb1 = dynamic_cast<B*>(pa);
if (pb1) {
cout << "pb1:" << pb1 << endl;
cout << pb1->_a << endl;
cout << pb1->_b << endl;
pb1->_a++;
pb1->_b++;
cout << pb1->_a << endl;
cout << pb1->_b << endl;
} else {
cout << "转换失败" << endl;
}
}
void fun3(A& pa) {
// 转换失败,则抛出 bad_cast 异常
try {
B& pb1 = dynamic_cast<B&>(pa);
cout << "转换成功" << endl;
} catch (const exception& e) {
cout << e.what() << endl;
}
}
int main() {
A a;
B b;
// fun1(&a);
// fun1(&b);
fun2(&a);
fun2(&b);
fun3(a);
fun3(b);
return 0;
}
运行时类型识别,它指的是程序在运行的时候才确定需要用到的对象是什么类型的。用于在运行时 (而不是编译时) 获取有关对象的信息。typeid 和 dynamic_cast; typeid 主要用于返回表达式的类型,dynamic_cast 前面已经介绍了,主要用于将基类的指针或者引用安全的转换成派生类的指针或者引用。typeid(e) 中 e 可以是任意表达式或类型的名字,typeid(e) 的返回值是 typeinfo 或 typeinfo 派生类对象的引用,typeinfo 可以只支持比较等于和不等于,name 成员函数可以返回 C 风格字符串表示对象类型名字,typeinfo 的精确定义随着编译器的不同而略有差异,也就意味着同一个 e 表达式,不同编译器下,typeid(e).name() 返回的名字可能是不一样的。typeid(e) 时,当运算对象不属于类类型或者是一个不包含任何虚函数的类时,typeid 返回的是运算对象的静态类型,当运算对象是定义了至少一个虚函数的类的左值时,typeid 的返回结果直到运行时才会求得。dynamic_cast 安全转型,避免野指针、非法访问。
❌缺点#include <iostream>
#include <string>
#include <vector>
#include <list>
using namespace std;
int main() {
int a[10];
int* ptr = nullptr;
cout << typeid(10).name() << endl;
cout << typeid(a).name() << endl;
cout << typeid(ptr).name() << endl;
cout << typeid(string).name() << endl;
cout << typeid(string::iterator).name() << endl;
cout << typeid(vector<int>).name() << endl;
cout << typeid(vector<int>::iterator).name() << endl;
return 0;
}
#include <iostream>
using namespace std;
class A {
public:
virtual void func() {}
protected:
int _a1 = 1;
};
class B : public A {
protected:
int _b1 = 2;
};
int main() {
try {
B* pb = new B;
A* pa = (A*)pb;
if (typeid(*pb) == typeid(B)) {
cout << "typeid(*pb) == typeid(B)" << endl;
}
// 如果 A 和 B 不是继承关系,则会抛 bad_typeid 异常
if (typeid(*pa) == typeid(B)) {
cout << "typeid(*pa) == typeid(B)" << endl;
}
// 这里 pa 和 pb 是 A*和 B*,不是类类型对象,它会被当做编译时求值的静态类型运算
// 所以这里始终是不相等的
if (typeid(pa) == typeid(pb)) {
cout << "typeid(pa) == typeid(B)" << endl;
}
} catch (const std::exception& e) {
cout << e.what() << endl;
}
return 0;
}

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online