深入解析 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.mzph.cn/bicheng/68078.shtml

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

相关文章

C++ STL之容器介绍(vector、list、set、map)

1 STL基本概念 C有两大思想&#xff0c;面向对象和泛型编程。泛型编程指编写代码时不必指定具体的数据类型&#xff0c;而是使用模板来代替实际类型&#xff0c;这样编写的函数或类可以在之后应用于各种数据类型。而STL就是C泛型编程的一个杰出例子。STL&#xff08;Standard …

uniapp 抖音小程序 getUserProfile:fail must be invoked by user tap gesture

项目场景&#xff1a; uniapp 抖音小程序 getUserProfile:fail must be invoked by user tap gesture,在实现点击头像需要出发抖音小程序获取用户原生头像的操作中&#xff0c;无论如何也无法触发抖音的原生窗口&#xff01; 问题描述 这个问题我找了很多博主的方法&#xff…

.NET Core NPOI 导出图片到Excel指定单元格并自适应宽度

NPOI&#xff1a;支持xlsx&#xff0c;.xls&#xff0c;版本>2.5.3 XLS&#xff1a;HSSFWorkbook&#xff0c;主要前缀HSS&#xff0c; XLSX&#xff1a;XSSFWorkbook&#xff0c;主要前缀XSS&#xff0c;using NPOI.XSSF.UserModel; 1、导出Excel添加图片效果&#xff0…

SpringCloud微服务:基于Nacos组件,整合Dubbo框架

dubbo和fegin的差异 一、Feign与Dubbo概述 Feign是一个声明式的Web服务客户端&#xff0c;使得编写HTTP客户端变得更简单。通过简单的注解&#xff0c;Feign将自动生成HTTP请求&#xff0c;使得服务调用更加便捷。而Dubbo是一个高性能、轻量级的Java RPC框架&#xff0c;提供了…

Jenkins触发器--在其他项目执行后构建

前言&#xff1a; jenkins中有多种触发器可用&#xff0c;可以方便的控制构建的启动 这里简单介绍下项目后构建的配置方法 1. 解释&#xff1a; Build after other projects are built Set up a trigger so that when some other projects finish building, a new build is…

4、蓝牙打印机-定时器驱动

蓝牙打印机实现打印内容&#xff0c;需要先通过定时器发送固定的节拍驱动步进电机转动&#xff0c;从而驱动打印头打印相应的内容。 因此想要实现打印&#xff0c;先要实现定时器功能。 本例采用通用定时器2完成发送节拍功能。 1、硬件 定时器总线 由上图知道TIM2是挂载在A…

(纯小白教程)Mac OS中安装配置Anaconda及常用conda命令回顾

本教程介绍了如何在Mac OS系统中下载、安装、配置 Anaconda3&#xff0c;并介绍了换源的方法&#xff0c;最后介绍了常用的conda命令。本教程适用于苹果系统&#xff0c;如需要在Windows系统或者Liunx系统中安装Anaconda3&#xff0c;可移步至主页的其他博客。 &#xff08;纯…

JVM面试相关

JVM组成 什么是程序计数器 详细介绍Java堆 什么是虚拟机栈 能不能解释一下方法区&#xff1f; 直接内存相关 类加载器 什么是类加载器&#xff0c;类加载器有哪些 什么是双亲委派模型 类加载过程 垃圾回收 对象什么时候可以被垃圾回收器回收 JVM垃圾回收算法有那些 JVM的分代…

Netty 入门学习

前言 学习Spark源码绕不开通信&#xff0c;Spark通信是基于Netty实现的&#xff0c;所以先简单学习总结一下Netty。 Spark 通信历史 最开始: Akka Spark 1.3&#xff1a; 开始引入Netty&#xff0c;为了解决大块数据&#xff08;如Shuffle&#xff09;的传输问题 Spark 1.6&…

[笔记] 使用 Jenkins 实现 CI/CD :从 GitLab 拉取 Java 项目并部署至 Windows Server

随着软件开发节奏的加快&#xff0c;持续集成&#xff08;CI&#xff09;和持续部署&#xff08;CD&#xff09;已经成为确保软件质量和加速产品发布的不可或缺的部分。Jenkins作为一款广泛使用的开源自动化服务器&#xff0c;为开发者提供了一个强大的平台来实施这些实践。然而…

qt QPainter setViewport setWindow viewport window

使用qt版本5.15.2 引入viewport和window目的是用于实现QPainter画出来的内容随着窗体伸缩与不伸缩两种情况&#xff0c;以及让QPainter在widget上指定的区域(viewport)进行绘制/渲染&#xff08;分别对应下方demo1&#xff0c;demo2&#xff0c;demo3&#xff09;。 setViewpo…

likeshop同城跑腿系统likeshop回收租赁系统likeshop多商户商城安装及小程序对接方法

前言&#xff1a;首先likeshop是一个开发平台&#xff0c;是一个独创的平台就像TP内核平台一样&#xff0c;你可以在这个平台上开发和衍生出很多伟大的产品&#xff0c;以likeshop为例&#xff0c;他们开发出商城系统&#xff0c;团购系统&#xff0c;外卖点餐系统&#xff0c;…

继续以“实用”指导Pythonic编码(re通配表达式)(2024年终总结②)

弃现成工具手剥任务&#x1f9d0;&#xff0c;我哈哈滴就像笨笨的傻大个儿&#x1f60b;。 (笔记模板由python脚本于2025年01月12日 23:29:33创建&#xff0c;本篇笔记适合熟悉正则表达式的coder翻阅) 【学习的细节是欢悦的历程】 Python官网&#xff1a;https://www.python.or…

相机和激光雷达的外参标定 - 无标定板版本

1. 实现的效果 通过本软件实现求解相机和LiDAR的外参&#xff0c;即2个传感器之间的三维平移[x, y, z]和三维旋转[roll, pitch, yaw]。完成标定后&#xff0c;可将点云投影到图像&#xff0c;效果图如下&#xff1a; 本软件的优势&#xff1a;&#xff08;1&#xff09;无需特…

WPF系列九:图形控件EllipseGeometry

简介 EllipseGeometry用于绘制一个椭圆的形状。它通常与其他图形元素结合使用&#xff0c;比如 Path 或者作为剪切区域来定义其他元素的外形。 定义椭圆&#xff1a;EllipseGeometry 用来定义一个椭圆或者圆的几何形状。参与绘制&#xff1a;可以被用作 Path 元素的数据&…

qt设置qwidget背景色无效

最近在做一个界面&#xff0c;有三个子窗体&#xff0c;于是就把各个子窗体分别做成了三个UI&#xff0c;再将3个UI&#xff0c;放到1个UI&#xff0c;再将那一个UI在其他窗体上进行提升。 最后就发现怎么设置qwidget的背景都没有效果。 在Qt中&#xff0c;如果是给Qwidget的…

【Rust学习笔记】Rust 的所有权介绍

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 博客内容主要围绕&#xff1a; 5G/6G协议讲解 高级C语言讲解 Rust语言讲解 文章目录 Rust中的所有权介绍1.1 一个简单的例子1.2 一个稍微复杂的例…

CentOS7下Hadoop集群分布式安装详细图文教程

1、集群规划 主机 角色 DSS20 NameNode DataNode ResourceManager NodeManager DSS21 SecondaryNameNode NameNode NodeManager DSS22 DataNode NodeManager 1.1、环境准备 1.1.1 关闭防火墙 #查看防火墙状态 firewall-cmd --state #停止…

在 Vue 项目中使用地区级联选

在 Vue 项目中使用地区级联选择的完整流程&#xff1a; 1.安装依赖包&#xff0c;这个包提供了中国省市区的完整数据。 npm install element-china-area-data --save 2.导入数据 import { regionData } from element-china-area-data 这个包提供了几种不同的数据格式&#…

基于改进粒子群优化的无人机最优能耗路径规划

目录 1. Introduction2. Preliminaries2.1. Particle Swarm Optimization Algorithm2.2. Deep Deterministic Policy Gradient2.3. Calculation of the Total Output Power of the Quadcopter Battery 3.OptimalEnergyConsumptionPathPlanningBasedonPSO-DDPG3.1.ProblemModell…