当前位置: 首页 > news >正文

深入解析 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;
  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;  // 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

  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 = 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)

  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.lryc.cn/news/520345.html

相关文章:

  • C++ union 联合(八股总结)
  • 聊聊AI Agent
  • scala代码打包配置(maven)
  • 慧集通(DataLinkX)iPaaS集成平台-业务建模之业务对象(二)
  • C++使用minio-cpp库在minio中创建bucket
  • 【大模型】大语言模型的数据准备:构建高质量训练数据的关键指南
  • 【解决】okhttp的java.lang.IllegalStateException: closed错误
  • TCP-IP详解卷 TCP的超时与重传
  • Linux服务器查看【可用端口号连接】的命令和方式【netstat,ss,lsof】
  • 【WPS】【WORDEXCEL】【VB】实现微软WORD自动更正的效果
  • Attention计算中的各个矩阵的维度都是如何一步步变化的?
  • 【数模学习笔记】插值算法和拟合算法
  • 探索 C++ 与 LibUSB:开启 USB 设备交互的奇幻之旅
  • 二、模型训练与优化(4):模型优化-实操
  • 3D可视化产品定制,应用于哪些行业领域?
  • Avalonia 入门笔记(零):概述
  • Unity TextMesh Pro入门
  • [论文阅读] (35)TIFS24 MEGR-APT:基于攻击表示学习的高效内存APT猎杀系统
  • 12 USART串口通讯
  • CF 368A.Sereja and Coat Rack(Java实现)
  • 清华大学、字节跳动等单位联合发布最新视觉语言动作模型RoboVLMs
  • 网络安全、Web安全、渗透测试之笔经面经总结
  • .NET Core NPOI 导出图片到Excel指定单元格并自适应宽度
  • python bs4 selenium 查找a href=javascript:();的实际点击事件和url
  • 三 BH1750 光感驱动调试1
  • UE材质节点Fresnel
  • linux的大内核锁与顺序锁
  • 用户注册模块用户校验(头条项目-05)
  • 面向对象的基本概念
  • 深度学习每周学习总结R4(LSTM-实现糖尿病探索与预测)