深入解析 C++ 类型转换

news2025/1/13 18:02:02

简介

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;
  1. 使用 var 创建 <type> 的临时变量
  2. <type> 可以是任何带有限定符的有效类型
  3. 通过更改变量中位的含义来覆盖类型系统
  4. 在某些情况下无法编译(稍后详细介绍)
  5. 支持在 constexpr 上下文中使用(稍后详细介绍)
  6. 可能导致未定义的行为
  7. 参与运算符优先级(级别 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 风格和函数式符号转换的问题
  1. 单一符号, 多重含义
  2. 容易出错
  3. 无法 grep
  4. 使 C 和 C++ 语法复杂化

C++ 强制转换的目标

  1. 不同的符号或不同的任务
  2. 易于识别和搜索
  3. 执行 C 强制转换可以执行的所有操作
  4. 消除意外错误
  5. 使强制转换不那么诱人

C++有如下几种类型转换的关键词:

  1. static_cast
  2. const_cast
  3. dynamic_cast
  4. reinterpret_cast

static_cast

T1 var;
T2 var2 = static_cast<T>(var)
  1. var 类型创建临时变量
  2. 尝试通过隐式和用户定义的转换或构造找到从 T1T2 的路径. 无法删除 const 限定.

使用场景:

  1. 阐明隐式转换

    int i = 1;
    double d = static_cast<double>(i);
    
  2. 指示有意截断

    int a = 1234;
    uint8_t u8 = static_cast<uint8_t>(a);
    
  3. 在基类和派生类之间进行强制转换

    struct Base {};
    struct Derived : public Base {};
    
    Derived derived;
    Base& rb = derived;
    Derived& rd = static_cast<Derived&>(rb);
    
  4. 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;
}
  1. A 有一个接受单个 int 的构造函数
  2. E 有一个用户定义的到 int 的转换
  3. 所以从ea的路径为: 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;  // OK

  d.f();  // Derived
  b.f();  // Derived

  Other& a = static_cast<Other&>(b);  // 危险, 转换到了其他类型
  a.f();                              // Derived
  static_assert(std::is_same<decltype(a), Other&>::value, "not the same");
  return 0;
}

const_cast

  1. 从变量中删除或添加 constvolatile 限定符, 不能更改类型
  2. 不会更改原始变量的 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 = 7
  int j = 4;
  const int* cj = &j;
  modify_pointer(const_cast<int*>(cj));
  std::cout << "i = " << i << std::endl;  // i = 7

  return 0;
}

输出

*p = 7
        modify_pointer *p <- 42
        modify_pointer *p = 42
i = 7
        modify_pointer *p <- 42
        modify_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)

  1. 为实现定义的结构中的每个多态类型存储额外信息
  2. 允许在运行时查询类型信息
  3. 可以禁用以节省空间(gcc/clang: –fno-rtti, msvc: /GR-)

dynamic_cast

  1. 查看 To 是否与 From 位于同一公共继承树中
  2. 只能是引用或指针
  3. 不能删除 CV
  4. From 必须是多态的
  5. 需要 RTTI
  6. 如果类型不相关, 则对指针返回 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;
}
  1. 可以将任何指针或引用类型更改为任何其他指针或引用类型
  2. 也称为类型双关
  3. 不能在 constexpr 上下文中使用
  4. 不能删除 CV 限定
  5. 不确保 To 和 From 的大小相同
  6. 适用于内存映射功能

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++会依次尝试:

  1. T conv = const_cast<T>(val);
  2. T conv = static_cast<T>(val);
  3. T conv = const_cast<T>(static_cast<const T>(val));
  4. T conv = reinterpret_cast<T>(val);
  5. T conv = const_cast<T>(reinterpret_cast<const T>(val));

如果找到匹配则会选择并执行编译, 否则会报错.

总结

C++ 提供了更安全, 更明确的类型转换工具, 开发者应根据场景选择合适的转换方式. 通过熟练掌握这些工具, 您可以编写更健壮, 更易维护的代码. 希望本博客能帮助您更深入地理解 C++ 类型转换的精髓!

参考资源

  • Back to Basics: Casting - Brian Ruth - CppCon 2021

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2276080.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

.NET framework、Core和Standard都是什么?

对于这些概念一直没有深入去理解&#xff0c;以至于经过.net这几年的发展进化&#xff0c;概念越来越多&#xff0c;越来越梳理不容易理解了。内心深处存在思想上的懒惰&#xff0c;以为自己专注于Unity开发就好&#xff0c;这些并不属于核心范畴&#xff0c;所以对这些概念总是…

【Java回顾】Day5 并发基础|并发关键字|JUC全局观|JUC原子类

JUC全称java.util.concurrent 处理并发的工具包(线程管理、同步、协调) 一.并发基础 多线程要解决什么问题&#xff1f;本质是什么&#xff1f; CPU、内存、I/O的速度是有极大差异的&#xff0c;为了合理利用CPU的高性能&#xff0c;平衡三者的速度差异&#xff0c;解决办法…

android framework.jar 在应用中使用

在开发APP中&#xff0c;有时会使用系统提供的framework.jar 来替代 android.jar, 在gradle中配置如下&#xff1a; 放置framework.jar 依赖配置 3 优先级配置 gradle.projectsEvaluated {tasks.withType(JavaCompile) {Set<File> fileSet options.bootstrapClasspat…

CHAIN OF RESPONSIBILITY(职责链)—对象行为型模式

1. 意图 使多个对象都有机会处理请求&#xff0c;从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链&#xff0c;并沿着这条链传递该请求&#xff0c;直到有一个对象处理它为止。 2. 动机 考虑一个图形用户界面中的上下文有关的帮助机制。用户在界面的任一部分…

Java高频面试之SE-11

hello啊&#xff0c;各位观众姥爷们&#xff01;&#xff01;&#xff01;本牛马baby今天又来了&#xff01;哈哈哈哈哈嗝&#x1f436; Java中是引用传递还是值传递&#xff1f; 在 Java 中&#xff0c;方法参数传递是通过 值传递 的方式实现的&#xff0c;但这可能会引起一…

VsCode对Arduino的开发配置

ps&#xff1a;我的情况是在对esp32进行编译、烧录时&#xff0c;找不到按钮&#xff0c;无法识别Arduino文件&#xff0c;适合已经有ini文件的情况。 1.在vscode中安装拓展 2.打开设置&#xff0c;点击右上角&#xff0c;转到settings.json文件 3.复制以下代码并保存 {"…

Apache Hop从入门到精通 第一课 揭开Apache Hop神秘面纱

一、Apache Hop是什么&#xff1f; 1、Apache Hop&#xff0c;简称Hop&#xff0c;全称为Hop Orchestration Platform&#xff0c;即Hop 工作编排平台&#xff0c;是一个数据编排和数据工程平台&#xff0c;旨在促进数据和元数据编排的所有方面。Hop让你专注于你想要解决的问题…

模拟SpringIOCAOP

一、IOC容器 Ioc负责创建&#xff0c;管理实例&#xff0c;向使用者提供实例&#xff0c;ioc就像一个工厂一样&#xff0c;称之为Bean工厂 1.1 Bean工厂的作用 先分析一下Bean工厂应具备的行为 1、需要一个获取实例的方法&#xff0c;根据一个参数获取对应的实例 getBean(…

基于ILI9341液晶屏+STM32U5单片的显示试验

试验要求&#xff1a; 1、通过串口&#xff0c;下发两个命令 STR和PIC&#xff1b; 2、STR模式&#xff1a; &#xff08;1&#xff09;串口输入什么&#xff0c;屏幕上显示什么 &#xff08;2&#xff09;如果屏幕满&#xff0c;自动下滚 &#xff08;3&#xff09;输入回车&a…

Elasticsearch:向量数据库基础设施类别的兴衰

过去几年&#xff0c;我一直在观察嵌入技术如何从大型科技公司的 “秘密武器” 转变为日常开发人员工具。接下来发生的事情 —— 向量数据库淘金热、RAG 炒作周期以及最终的修正 —— 教会了我们关于新技术如何在更广泛的生态系统中找到一席之地的宝贵经验。 更多有关向量搜索…

《系统爆破:MD5易破,后台登录可爆破?》

声明&#xff1a;笔记的只是方便各位师傅学习知识&#xff0c;以下代码、网站只涉及学习内容&#xff0c;其他的都与本人无关&#xff0c;切莫逾越法律红线&#xff0c;否则后果自负。 爆破Sales系统 一、爆破MD5 场景&#xff1a;已知MD5的加密字符串&#xff0c;如何得知明…

《Spring Framework实战》14:4.1.4.5.自动装配合作者

欢迎观看《Spring Framework实战》视频教程 自动装配合作者 Spring容器可以自动连接协作bean之间的关系。您可以通过检查ApplicationContext的内容&#xff0c;让Spring自动为您的bean解析协作者&#xff08;其他bean&#xff09;。自动装配具有以下优点&#xff1a; 自动装配…

GitLab CI/CD使用runner实现自动化部署前端Vue2 后端.Net 7 Zr.Admin项目

1、查看gitlab版本 建议安装的runner版本和gitlab保持一致 2、查找runner 执行 yum list gitlab-runner --showduplicates | sort -r 找到符合gitlab版本的runner&#xff0c;我这里选择 14.9.1版本 如果执行出现找不到下载源&#xff0c;添加官方仓库 执行 curl -L &quo…

冒泡排序基础与实现

目录 1. 原理图 ​编辑 2. 什么是冒泡排序 3. 工作原理 3.1 具体步骤 3.2 时间复杂度 3.3 空间复杂度 4. 代码实现 5. 总结 1. 原理图 2. 什么是冒泡排序 冒泡排序&#xff08;Bubble Sort&#xff09;是一种简单的排序算法&#xff0c;它通过重复地遍历要排序的列表&am…

acwing_5722_十滴水

acwing_5722_十滴水 下面这篇大佬的题解属实是把指针用明白了&#xff0c;可以好好理解一下&#xff1a; 原题解连接&#xff1a;AcWing 5722. 一个简单模拟实现 - AcWing map/unordered_map的用法:见收藏夹 #include<iostream> #include<unordered_map> #incl…

【AI进化论】 AI微信机器人 | sealos + 智能微秘书 打造AI机器人 | 智能微秘书配置教程

一、sealos 什么是sealos &#xff1f; One cloud OS for all applications 1、创建sealos账号密码 根据链接&#xff08;帮我凑点sealos使用额度感谢&#xff09;&#xff1a;https://cloud.sealos.run/?uidXfUpoQk92c 登录后如下页面&#xff1a; 2、创建应用 点击【应…

Agentless:OpenAI 采用的非代理框架

不需要代理库来解决复杂的业务问题。Agentless 是OpenAI采用的非代理框架&#xff0c;用于在 o3 的 SWE Bench 上实现最高精度。SWE-bench 是 github的真实软件工程问题基准。Agentless 遵循简单的三阶段流程&#xff1a;本地化、修复和补丁验证&#xff1a; 1 ⃣生成存储库的…

Model-based RL自动出价算法的演进之路

▐ 导读 近年来&#xff0c;强化学习自动出价算法已成为智能投放领域的标志性技术&#xff0c;然而其所存在的在离线不一致、线上数据覆盖空间受限等关键问题尚未被完全解决。在本文中&#xff0c;我们提出一种Model-based RL&#xff08;MBRL&#xff09;自动出价算法训练新范…

【Cocos TypeScript 零基础 7.1】

目录 重写 小结一下心得页面跳转背景移动精简 player敌机精灵 重写 小结一下心得 本人重写了整个项目 有了点小心得 页面跳转 director.loadScene(s2)背景移动 canvas 是画布 为什么要向上图布局? 方便计算相对坐标,脚本还是只写一个 绑定上 BG 一样跑,不影响 export cl…

鸿蒙UI(ArkUI-方舟UI框架)

参考&#xff1a;https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V13/arkts-layout-development-overview-V13 ArkUI简介 ArkUI&#xff08;方舟UI框架&#xff09;为应用的UI开发提供了完整的基础设施&#xff0c;包括简洁的UI语法、丰富的UI功能&#xff…