深入解析 C++ 类型转换
简介
C++ 类型转换是开发者必须掌握的重要技能之一, 无论是处理隐式转换还是显式转换, 理解其背后的机制与用法至关重要. 本篇博客旨在从基础到高级全面解析 C++ 的类型转换, 包括实际开发中的应用场景和性能分析.
自动转换
隐式类型转换
编译器可以在无需明确指示的情况下, 将一种类型的值自动转换为另一种兼容类型. 例如:
struct Struct {Struct(float f) : m_(f) {}float m_ = 0;
};
float f = -3.1415926;
double d = f;
int i = f;
size_t s = f;
char c = f;
Struct st = f;
| 赋值语句 | 值 |
|---|---|
float f = -3.14; | -3.14159 |
double d = f; | -3.14159 |
int i = f; | -3 |
size_t s = f; | 18446744073709551613 |
char c = f; | 乱码 |
Struct st = f; | {.m_ = -3.14159} |
算术类型转换
void drawLine(uint8_t start, uint8_t end);
uint8_t x = 10;
uint8_t width = 50;
// 此处等价于: drawLine(x, static_cast<unsigned char>(static_cast<int>(x) + static_cast<int>(width)));
drawLine(x, x+width);
符号转换
void print(const std::vector<int>& vec) {// 此处等价于: static_cast<unsigned long>(i) < vec.size()for (int i = 0; i < vec.size(); i++) {std::cout << i << ",";}
}
用户转换运算符
class Struct {public:Struct(float f) : m_(f) {}// 重载了转为int类型的操作符operator int() const { return m_; }private:float m_ = 0;
};int main() {Struct si(1);int i = si;
}
显示类型转换
C 风格类型转换
(type)var;
- 使用
var创建<type>的临时变量 <type>可以是任何带有限定符的有效类型- 通过更改变量中位的含义来覆盖类型系统
- 在某些情况下无法编译(稍后详细介绍)
- 支持在
constexpr上下文中使用(稍后详细介绍) - 可能导致未定义的行为
- 参与运算符优先级(级别 3)
struct A {};
struct B {};int main() {float f = 7.406f;int i = (int)f; // int i = static_cast<int>(f);A* pa = (A*)&f; // A* pa = reinterpret_cast<A*>(&f);B* pb = (B*)pa; // B* pb = reinterpret_cast<B*>(pa);double d = *(double*)(pb); // double d = *reinterpret_cast<double*>((pb));return 0;
}
C 风格和函数式符号转换的问题
- 单一符号, 多重含义
- 容易出错
- 无法
grep - 使 C 和 C++ 语法复杂化
C++ 强制转换的目标
- 不同的符号或不同的任务
- 易于识别和搜索
- 执行 C 强制转换可以执行的所有操作
- 消除意外错误
- 使强制转换不那么诱人
C++有如下几种类型转换的关键词:
static_castconst_castdynamic_castreinterpret_cast
static_cast
T1 var;
T2 var2 = static_cast<T>(var)
- 从
var类型创建临时变量 - 尝试通过隐式和用户定义的转换或构造找到从
T1到T2的路径. 无法删除const限定.
使用场景:
-
阐明隐式转换
int i = 1; double d = static_cast<double>(i); -
指示有意截断
int a = 1234; uint8_t u8 = static_cast<uint8_t>(a); -
在基类和派生类之间进行强制转换
struct Base {}; struct Derived : public Base {};Derived derived; Base& rb = derived; Derived& rd = static_cast<Derived&>(rb); -
在
void*和T*之间进行强制转换struct MyStruct {}; void callback(void* handle) {auto p = static_cast<MyStruct*>(handle);//... }
多重转换
#include <cstdio>struct A {explicit A(int) { puts("A"); }
};
struct E {operator int() {puts("B::operator int");return 0;}
};int main() {E e;A a = static_cast<A>(e);return 0;
}
A有一个接受单个int的构造函数E有一个用户定义的到int的转换- 所以从
e到a的路径为:e->int->a
static_cast 与继承
#include <iostream>struct B1 {virtual ~B1() = default;int i;
};struct B2 {virtual ~B2() = default;int j;
};struct Derived : public B1, public B2 {int k;
};void Compare(void* p1, void* p2) {if (p1 == p2) {std::cout << "Same.\n";} else {std::cout << "Different.\n";}
}int main() {Derived d;// pd 指向派生类Derived* pd = &d;// pb1 是指向基类B1的指针B1* pb1 = static_cast<B1*>(&d);Compare(pd, pb1); // Same.// pb2 是指向基类B1的指针B2* pb2 = static_cast<B2*>(&d);Compare(pd, pb2); // Different.void* derived_plus_offset = (char*)pd + sizeof(B1);Compare(derived_plus_offset, pb2); // Same.return 0;
}
为什么会出现这样的情况? 因为Derived的布局为:
+---------+ <--- pd and pb1
| B1 |
+---------+ <--- pb2
| B2 |
+---------+
| Derived |
+---------+...
static_cast 并非绝对正确
static_cast 无法防止向下转型为不相关的类型
#include <iostream>
#include <type_traits>struct Base {virtual void f() { std::cout << "base\n"; }virtual ~Base() = default;
};
struct Derived : public Base {void f() override { std::cout << "Derived\n"; }
};struct Other : public Base {void f() override { std::cout << "Other\n"; }
};int main() {Derived d;Base& b = d; // OKd.f(); // Derivedb.f(); // DerivedOther& a = static_cast<Other&>(b); // 危险, 转换到了其他类型a.f(); // Derivedstatic_assert(std::is_same<decltype(a), Other&>::value, "not the same");return 0;
}
const_cast
- 从变量中删除或添加
const或volatile限定符, 不能更改类型 - 不会更改原始变量的 CV 限定符
#include <iostream>void use_pointer(int* p) { std::cout << "*p = " << *p << std::endl; }
void modify_pointer(int* p) {*p = 42;std::cout << "\tmodify_pointer *p <- 42\n"<< "\tmodify_pointer *p = " << *p << std::endl;
}int main() {const int i = 7;use_pointer(const_cast<int*>(&i));modify_pointer(const_cast<int*>(&i));std::cout << "i = " << i << std::endl; // i = 7int j = 4;const int* cj = &j;modify_pointer(const_cast<int*>(cj));std::cout << "i = " << i << std::endl; // i = 7return 0;
}
输出
*p = 7modify_pointer *p <- 42modify_pointer *p = 42
i = 7modify_pointer *p <- 42modify_pointer *p = 42
i = 7
可以看到虽然在函数modify_pointer里面指针指向的值发生了变化, 但是在外面的值却不受影响.
const_cast example: member overload
#include <stddef.h>class my_array {public:char& operator[](size_t offset) {// 此处调用const版本的实现, 避免重写一遍逻辑.return const_cast<char&>(const_cast<const my_array&>(*this)[offset]);}const char& operator[](size_t offset) const { return buffer[offset]; }private:char buffer[10];
};
int main() {const my_array a{};const auto& c = a[4];my_array mod_a;mod_a[4] = 7;return 0;
}
用于防止成员函数的代码重复.
运行时类型信息 (RTTI)
- 为实现定义的结构中的每个多态类型存储额外信息
- 允许在运行时查询类型信息
- 可以禁用以节省空间(gcc/clang:
–fno-rtti, msvc:/GR-)
dynamic_cast
- 查看 To 是否与 From 位于同一公共继承树中
- 只能是引用或指针
- 不能删除 CV
- From 必须是多态的
- 需要 RTTI
- 如果类型不相关, 则对指针返回 nullptr, 对引用抛出
std::bad_cast
#include <cstdio>
#include <vector>struct A {virtual ~A() = default;
};
struct B : public A {};
struct C : public A {};
int main() {C c;B b;std::vector<A*> a_list = {&c, &b};for (size_t i = 0; i < a_list.size(); ++i) {A* pa = a_list[i];if (dynamic_cast<B*>(pa)) {printf("a_list[%lu] was a B\r\n", i);}if (dynamic_cast<C*>(pa)) {printf("a_list[%lu] was a C\r\n", i);}}return 0;
}
dynamic_cast 用例: UI 框架
struct Widget {};
struct Label : public Widget {};
struct Button : public Widget { void DoClick(); };
dynamic_cast can be expensive
from gcc’s rtti.c
reinterpret_cast
#include <cstdint>struct A {};
struct B {int i;int j;
};int main() {int i = 0;int* pi = &i;uintptr_t uipt = reinterpret_cast<uintptr_t>(pi);float& f = reinterpret_cast<float&>(i);A a;B* pb = reinterpret_cast<B*>(&a);char buff[10];B* b_buff = reinterpret_cast<B*>(buff);return 0;
}
- 可以将任何指针或引用类型更改为任何其他指针或引用类型
- 也称为类型双关
- 不能在 constexpr 上下文中使用
- 不能删除 CV 限定
- 不确保 To 和 From 的大小相同
- 适用于内存映射功能
reinterpret_cast 访问私有继承的基类
struct B {void m() { puts("private to D"); }
};
struct D : private B {};
int main() {D d;B& b = reinterpret_cast<B&>(d);b.m();return 0;
}
Type Aliasing
当两种类型的内存布局兼容时, 将一种类型的内存当作另一种类型的内存来使用的行为.
compatible types
struct Point {int x;int y;
};
struct Location {int x;int y;
};
Point p{1, 2};
auto* loc = reinterpret_cast<Location*>(&p);
incompatible types
float f = 1.0f;
int* i = reinterpret_cast<int*>(&f);
C 风格类型转换在 C++ 中是如何实际执行的
对与一个类型转换
T conv = (T)val;
C++会依次尝试:
T conv = const_cast<T>(val);T conv = static_cast<T>(val);T conv = const_cast<T>(static_cast<const T>(val));T conv = reinterpret_cast<T>(val);T conv = const_cast<T>(reinterpret_cast<const T>(val));
如果找到匹配则会选择并执行编译, 否则会报错.
总结
C++ 提供了更安全, 更明确的类型转换工具, 开发者应根据场景选择合适的转换方式. 通过熟练掌握这些工具, 您可以编写更健壮, 更易维护的代码. 希望本博客能帮助您更深入地理解 C++ 类型转换的精髓!
参考资源
- Back to Basics: Casting - Brian Ruth - CppCon 2021
相关文章:
深入解析 C++ 类型转换
简介 C 类型转换是开发者必须掌握的重要技能之一, 无论是处理隐式转换还是显式转换, 理解其背后的机制与用法至关重要. 本篇博客旨在从基础到高级全面解析 C 的类型转换, 包括实际开发中的应用场景和性能分析. 自动转换 隐式类型转换 编译器可以在无需明确指示的情况下, 将一…...
C++ union 联合(八股总结)
union(联合体)允许在同一内存位置上存储不同的数据类型,所有成员共享相同的内存空间。 内存布局 由于联合体的所有成员都共享同一块内存,因此联合体的大小是其最大成员的大小。联合体的实际大小取决于其最大成员的类型和对齐要求…...
聊聊AI Agent
什么是AI Agent? AI Agent指的是一种使用人工智能技术的自主实体,它能够感知环境、做出决策,并采取行动以实现特定目标。AI Agent的核心思想是它能够独立运作,基于输入信息做出有根据的决策,并通过学习算法不断提高自…...
scala代码打包配置(maven)
目录 mavenpom.xml打包配置项(非完整版,仅含打包的内容< build>)pom.xml完整示例(需要修改参数)效果说明 maven 最主要的方式还是maven进行打包,也好进行配置项的管理 以下为pom文件(不要…...
慧集通(DataLinkX)iPaaS集成平台-业务建模之业务对象(二)
3.UI模板 当我们选择一条已经建好的业务对象点击功能按钮【UI模板】进入该业务对象的UI显示配置界面。 右边填写的是UI模板的编码以及对应名称;菜单界面配置以业务对象UI模板编码获取显示界面。 3.1【列表-按钮】 展示的对应业务对象界面的功能按钮配置࿱…...
C++使用minio-cpp库在minio中创建bucket
直接看代码 #include <iostream> #include <string>#include "miniocpp/client.h"int main() {minio::s3::BaseUrl baseUrl("base url");minio::creds::StaticProvider staticProvider("access key", "secret key");mini…...
【大模型】大语言模型的数据准备:构建高质量训练数据的关键指南
大语言模型的数据准备:构建高质量训练数据的关键指南 大语言模型(LLM, Large Language Model)的训练离不开高质量的数据,而数据准备是模型性能的基石。无论是预训练还是微调,数据的选择、清洗和标注都会直接影响模型的…...
【解决】okhttp的java.lang.IllegalStateException: closed错误
问题 Android 使用OKHttp进行后端通信,后端处理结果,反馈给前端的responseBody中其实有值,但是一直报异常,后来才发现主要是OkHttp请求回调中response.body().string()只能有效调用一次,而我使用了两次: 解…...
TCP-IP详解卷 TCP的超时与重传
TCP-IP详解卷1-21:TCP的超时与重传(Timeout and Retransmission) 一:介绍 1: 与数据链路层的ARQ协议相类似,TCP使用超时重发的重传机制。 即:TCP每发送一个报文段,就对此报文段设置…...
Linux服务器查看【可用端口号连接】的命令和方式【netstat,ss,lsof】
Linux服务器查看可用连接的端口号的命令和方式 前言:1. 使用netstat命令(netstat命令详解及使用指南)一、什么是netstat二、基本使用方法与参数解释三、输出结果字段含义:四、查找可用于SSH连接的端口示例五、部分高级用法&#x…...
【WPS】【WORDEXCEL】【VB】实现微软WORD自动更正的效果
1. 代码规范方面 添加 Option Explicit:强制要求显式声明所有变量,这样可以避免因变量名拼写错误等情况而出现难以排查的逻辑错误,提高代码的健壮性。使用 On Error GoTo 进行错误处理:通过设置错误处理机制,当代码执行…...
Attention计算中的各个矩阵的维度都是如何一步步变化的?
在Transformer模型中,各个矩阵的维度变化是一个关键的过程,涉及到输入、编码器、解码器和输出等多个阶段。以下是详细的维度变化过程: 输入阶段 输入序列:假设输入序列的长度为seq_len,每个单词或标记通过词嵌入&…...
【数模学习笔记】插值算法和拟合算法
声明:以下笔记中的图片以及内容 均整理自“数学建模学习交流”清风老师的课程资料,仅用作学习交流使用 文章目录 插值算法定义三个类型插值举例插值多项式分段插值三角插值 一般插值多项式原理拉格朗日插值法龙格现象分段线性插值 牛顿插值法 Hermite埃尔…...
探索 C++ 与 LibUSB:开启 USB 设备交互的奇幻之旅
一、引言 在当今数字化时代,USB(通用串行总线)设备无处不在,从常见的 U 盘、鼠标、键盘,到复杂的工业数据采集设备、医疗监测仪器等,它们以方便快捷的插拔式连接,为人们的生活和工作带来了极大…...
二、模型训练与优化(4):模型优化-实操
下面我将以 MNIST 手写数字识别模型为例,从 剪枝 (Pruning) 和 量化 (Quantization) 两个常用方法出发,提供一套可实际动手操作的模型优化流程。此示例基于 TensorFlow/Keras 环境,示范如何先训练一个基础模型,然后对其进行剪枝和…...
3D可视化产品定制,应用于哪些行业领域?
3D可视化定制服务已广泛渗透至众多行业领域,包括汽车、家居、时尚鞋服、珠宝配饰以及数码电器等: 汽车行业: 借助Web全景技术与3D模型,我们高保真地再现了汽车外观,为用户带来沉浸式的车型浏览体验。用户可在展示界面自…...
Avalonia 入门笔记(零):概述
Avalonia 是一个基于 .NET 和 Skia 的开源、跨平台 UI 框架,支持 Windows、Linux、macOS、iOS、Android 和 WebAssembly。Skia 是一个基于 C 的开源 2D 渲染引擎,Avalonia 通过 Skia 自绘 UI 控件,保证在全平台具有一致的观感 基于 .NET 的跨…...
Unity TextMesh Pro入门
概述 TextMesh Pro是Unity提供的一组工具,用于创建2D和3D文本。与Unity的UI文本和Text Mesh系统相比,TextMesh Pro提供了更好的文本格式控制和布局管理功能。 本文介绍了TMP_Text组件和Tmp字体资产(如何创建字体资产和如何解决缺字问题),还有一些高级功…...
[论文阅读] (35)TIFS24 MEGR-APT:基于攻击表示学习的高效内存APT猎杀系统
《娜璋带你读论文》系列主要是督促自己阅读优秀论文及听取学术讲座,并分享给大家,希望您喜欢。由于作者的英文水平和学术能力不高,需要不断提升,所以还请大家批评指正,非常欢迎大家给我留言评论,学术路上期…...
12 USART串口通讯
1 串口物理层 两个设备的“DB9接口”之间通过串口信号建立连接,串口信号线中使用“RS232标准”传输数据信号。由于RS232电平标准的信号不能直接被控制器直接识别,所以这些信号会经过“电平转换芯片”转换成控制器能识别的“TTL校准”的电平信号ÿ…...
Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...
PostgreSQL——环境搭建
一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在࿰…...
[论文阅读]TrustRAG: Enhancing Robustness and Trustworthiness in RAG
TrustRAG: Enhancing Robustness and Trustworthiness in RAG [2501.00879] TrustRAG: Enhancing Robustness and Trustworthiness in Retrieval-Augmented Generation 代码:HuichiZhou/TrustRAG: Code for "TrustRAG: Enhancing Robustness and Trustworthin…...
LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)
在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...
Axure 下拉框联动
实现选省、选完省之后选对应省份下的市区...
Linux安全加固:从攻防视角构建系统免疫
Linux安全加固:从攻防视角构建系统免疫 构建坚不可摧的数字堡垒 引言:攻防对抗的新纪元 在日益复杂的网络威胁环境中,Linux系统安全已从被动防御转向主动免疫。2023年全球网络安全报告显示,高级持续性威胁(APT)攻击同比增长65%,平均入侵停留时间缩短至48小时。本章将从…...
