聊聊 C++ 中的四种类型转换符

一:背景

在玩 C 的时候,经常会用 void* 来指向一段内存地址开端,然后再将其强转成尺度更小的 char*int* 来丈量一段内存,参考如下代码:


int main()
{
    void* ptr = malloc(sizeof(int) * 10);

    int* int_ptr = (int*)ptr;
    char* char_ptr = (char*)ptr;
}

由于 C 的自由度比较大,想怎么玩就怎么玩,带来的弊端就是容易隐藏着一些不易发现的bug,归根到底还是程序员的功底不扎实,C++ 设计者觉得不能把程序员想的太厉害,应该要力所能及的帮助程序员避掉一些不必要的潜在 bug,并且还要尽最大努力的避免对性能有过多的伤害,所以就出现了 4 个强制类型转换运算符。

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

既然 C++ 做了归类,必然就有其各自用途,接下来我们逐一和大家聊一下。

二:理解四大运算符

1. const_cast

这是四个运算符中最好理解的,玩过 C++ 的都知道,默认情况下是不能修改一个 const 变量,比如下面这样:


int main()
{
    const int i = 10;
    i = 12;
}

这段代码肯定是要报错的,那如果我一定要实现这个功能,如何做呢?这就需要用到 const_cast 去掉它的常量符号,然后对 i 进行操作即可,所以修改代码如下:


int main()
{
    const int i = 10;
    auto j = const_cast(&i);
    *(j) = 12;
}

聊聊 C++ 中的四种类型转换符

2. reinterpret_cast

从名字上看就是一个 重新解释转换,很显然这个非常底层,如果大家玩过 windbg ,应该知道用 dt 命令可以将指定的内存地址按照某一个结构体丈量出来,比如说 C# 的 CLR 在触发 GC 时,会有 gc_mechanisms 结构,参考代码如下:


0:000> dt WKS::gc_mechanisms 0x7ffb6ba96e60
coreclr!WKS::gc_mechanisms
   +0x000 gc_index         : 1
   +0x008 condemned_generation : 0n0
   +0x00c promotion        : 0n0
   +0x010 compaction       : 0n1
   +0x014 loh_compaction   : 0n0
   +0x018 heap_expansion   : 0n0
   +0x01c concurrent       : 0
   +0x020 demotion         : 0n0
   +0x024 card_bundles     : 0n1
   +0x028 gen0_reduction_count : 0n0
   +0x02c should_lock_elevation : 0n0
   +0x030 elevation_locked_count : 0n0
   +0x034 elevation_reduced : 0n0
   +0x038 minimal_gc       : 0n0
   +0x03c reason           : 0 ( reason_alloc_soh )
   +0x040 pause_mode       : 1 ( pause_interactive )
   +0x044 found_finalizers : 0n0
   +0x048 background_p     : 0n0
   +0x04c b_state          : 0 ( bgc_not_in_process )
   +0x050 allocations_allowed : 0n1
   +0x054 stress_induced   : 0n0
   +0x058 entry_memory_load : 0
   +0x05c exit_memory_load : 0

其实 reinterpret_cast 大概也是干这个事的,参考代码如下:


typedef struct _Point {
    int x;
    int y;
} Point;

int main()
{
    Point point = { 10,11 };

    //内存地址
    void* ptr = &point;

    //根据内存地址 丈量出  Point
    Point* ptr_point = reinterpret_cast(ptr);

    printf("x=%d", ptr_point->x);
}

从代码看,我直接根据 ptr 地址丈量出了 Point 结构,说实话这个和 C 玩法就比较类似了。

3. dynamic_cast

在多态场景下,有时候会遇到这样的一个问题,一个父类有多个子类,我现在手拥一个父类,我不知道能不能将它转换为其中一个子类,要试探一下看看,那怎么去试探呢? 类似 C# 中的 as 运算符,在 C++ 中就需要用 dynamic_cast 来做这件事情,参考如下:


//点
class Point {
public:
    Point(int x, int y) :x(x), y(y) {}
    virtual void show() {}
public:
    int x;
    int y;
};

//矩形
class Rectangle :public Point {
public:
    Rectangle(int x, int y, int w, int h) : Point(x, y), w(w), h(h) {}
public:
    int w;
    int h;
};

//三角形
class Triangle :public Point {
public:
    Triangle(int x, int y, int z) :Point(x, y), z(z) {}
public:
    int z;
};

int main()
{
    Point* p1 = new Rectangle(10, 20, 100, 200);
    Point* p2 = new Triangle(4, 5, 6);

    //将  p1 转成 子类 Triangle 会报错的
    Triangle* t1 = dynamic_cast(p1);

    if (t1 == nullptr) {
        printf("p1 不能转成 Triangle");
    }
}

聊聊 C++ 中的四种类型转换符

对,场景就是这个,p1 其实是 Rectangle 转上去的, 这时候你肯定是不能将它向下转成 Triangle , 问题就在这里,很多时候你并不知道此时的 p1 是哪一个子类。

接下来的一个问题是,C++ 并不像C# 有元数据,那它是如何鉴别呢? 其实这用了 RTTI 技术,哪里能看出来呢?哈哈,看汇编啦。


    Triangle* t1 = dynamic_cast(p1);
00831D57  push        0
00831D59  push        offset Triangle RTTI Type Descriptor' (083C150h)
00831D5E  push        offset Point  Type Descriptor' (083C138h)
00831D63  push        0
00831D65  mov         eax,dword ptr [p1]
00831D68  push        eax
00831D69  call        ___RTDynamicCast (083104Bh)
00831D6E  add         esp,14h
00831D71  mov         dword ptr [t1],eax

从汇编可以看到编译器这是带夹私货了,在底层偷偷的调用了一个 ___RTDynamicCast 函数在运行时帮忙检测的,根据 cdcel 调用协定,参数是从右到左,恢复成代码大概是这样。


___RTDynamicCast(&p1, 0, &Point, &Triangle,0)

3. static_cast

从名字上就能看出,这个强转具有 static 语义,也就是 编译阶段 就生成好了,具体安全不安全,它就不管了,就拿上面的例子,将 dynamic_cast 改成 static_cast 看看有什么微妙的变化。


int main()
{
    Point* p1 = new Rectangle(10, 20, 100, 200);
    Point* p2 = new Triangle(4, 5, 6);

    Triangle* t1 = static_cast(p1);

    printf("x=%d, y=%d,z=%d", t1->x, t1->y, t1->z);
}

聊聊 C++ 中的四种类型转换符

我们发现居然转成功了,而且 Triangle 的值也是莫名奇怪,直接取了 Rectangle 的前三个值,如果这是生产代码,肯定要 挨批了。。。

接下来简单看下汇编代码:


    Triangle* t1 = static_cast(p1);
00DF5B17  mov         eax,dword ptr [p1]
00DF5B1A  mov         dword ptr [t1],eax

    printf("x=%d, y=%d,z=%d", t1->x, t1->y, t1->z);
00DF5B1D  mov         eax,dword ptr [t1]
00DF5B20  mov         ecx,dword ptr [eax+0Ch]
00DF5B23  push        ecx
00DF5B24  mov         edx,dword ptr [t1]
00DF5B27  mov         eax,dword ptr [edx+8]
00DF5B2A  push        eax
00DF5B2B  mov         ecx,dword ptr [t1]
00DF5B2E  mov         edx,dword ptr [ecx+4]
00DF5B31  push        edx
00DF5B32  push        offset string "x=%d, y=%d,z=%d" (0DF8C80h)
00DF5B37  call        _printf (0DF145Bh)
00DF5B3C  add         esp,10h

从代码中看,它其实就是将 p1 的首地址给了 t1,然后依次把copy偏移值 +4,+8,+0C, 除了转换这个,还可以做一些 int ,long ,double 之间的强转,当然也是一样,编译时汇编代码就已经生成好了。

好了,本篇就说这么多,希望对你有帮助。

Original: https://www.cnblogs.com/huangxincheng/p/16480503.html
Author: 一线码农
Title: 聊聊 C++ 中的四种类型转换符

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/536395/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • C++面试题1

    1,LeetCode给出一个 32 位的有符号整数,将这个整数中每位上的数字进行反转; 2,怎么判断一个变量是指针; Original: https://www.cnblogs.c…

    C++ 2023年5月29日
    053
  • C++入门笔记

    一直对C++感到很恐惧,大学里有C的基础,今天终于鼓足勇气入门C++,先大致了解一下,以后用到的时候再详细深入。 Android中有一些很火的领域比如:音视频、物联网,都会涉及到J…

    C++ 2023年5月29日
    058
  • EclipseC++学习笔记-6 自动补头文件

    在报错代码处source->add Include 本博客是个人工作中记录,遇到问题可以互相探讨,没有遇到的问题可能没有时间去特意研究,勿扰。另外建了几个QQ技术群:2、全栈…

    C++ 2023年5月29日
    047
  • 面向对象C++编程与实践考试题目

    一、选择题(每题2分,共2×20=40分) (1) 以下不能对数组 a 进行正确初始化的语句是( )。A. int a[2][3] = { 0 };B. int a[ ][3] =…

    C++ 2023年5月29日
    037
  • C++/CLI

    【 C++/CLI】 A C++/CLI application or component uses extensions to C++ syntax (as allowed by…

    C++ 2023年5月29日
    061
  • C++ lambda 用法

    为什么要使用lambda 就地匿名的定义一个目标函数或者函数对象,不需要额外的再写一个命名函数或者函数对象,以更直接的方式去写函数,可以调高程序的可读性和可维护性。 简洁:不要额外…

    C++ 2023年5月29日
    060
  • 用C++实现半透明按钮控件(PNG,GDI+)

    使用MFC实现上面的按钮半透明效果能看到父窗口中的内容,上面是效果图(一个是带背景图片的、另一个是不带的)。 控件继承自CWnd类(彩色的部分是窗口的背景图片、按钮是PNG图片,第…

    C++ 2023年5月29日
    075
  • 汉诺塔的c++实现

    void hanNuoTa(int n,int a,int b,int c) { if (n == 0) return; hanNuoTa(n – 1, a, c, b); cou…

    C++ 2023年5月29日
    058
  • VS Code C++ 代码格式化方法(clang-format)

    转自:https://blog.csdn.net/core571/article/details/82867932?depth_1-utm_source=distribute.pc…

    C++ 2023年5月29日
    0101
  • clion运行单个c和c++文件(.c.cpp)

    运行方法 在clion中安装插件:C/C++Single File Execution 在要执行的cpp文件中添加main函数 在cpp文件的编辑器界面中点右键会出现【Add ex…

    C++ 2023年5月29日
    058
  • Xcode 导出C++项目在其他电脑执行

    先找到C++项目的可执行文件的位置 https://blog.csdn.net/qq_34759481/article/details/82700587 关于存储和加载文件的目录,…

    C++ 2023年5月29日
    052
  • C++深拷贝与浅拷贝

    对于普通类型的对象来说,它们之间的复制是很简单的,例如:int a=88;int b=a;而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量。下面看一个类对象拷贝…

    C++ 2023年5月29日
    032
  • 配置 Windows 下的 nodejs C++ 模块编译环境

    Python 安装 python-2.7.7.msi iso 虚拟磁盘 安装 DTLite4491-0356.exe Windows XP 用 DTLite 打开 VS2010Ex…

    C++ 2023年5月29日
    052
  • C++中函数调用时的三种参数传递方式详解

    https://blog.csdn.net/zeng_jun_yv/article/details/98868159?utm_medium=distribute.pc_releva…

    C++ 2023年5月29日
    076
  • [C++] const和mutable关键字使用方法

    const 修饰的变量为常成员变量,表示此变量不能被修改赋值,并且构造函数中只能用初始化列表的方式初始化,其他初始化方式都是错误的 const 修饰的函数为常成员函数,表示此函数中…

    C++ 2023年5月29日
    058
  • C/C++中static,const,inline三种关键字的总结(参照网络)

    一、 关于staticstatic 是C++中很常用的修饰符,它被用来控制变量的存储方式和可见性,下面我将从 static 修饰符的产生原因、作用谈起,全面分析static 修饰符…

    C++ 2023年5月29日
    063
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球