一:背景

在玩 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<int*>(&i);
*(j) = 12;
}

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<Point*>(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<Triangle*>(p1); if (t1 == nullptr) {
printf("p1 不能转成 Triangle");
}
}

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

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


Triangle* t1 = dynamic_cast<Triangle*>(p1);
00831D57 push 0
00831D59 push offset Triangle `RTTI Type Descriptor' (083C150h)
00831D5E push offset Point `RTTI 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<Triangle*>(p1); printf("x=%d, y=%d,z=%d", t1->x, t1->y, t1->z);
}

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

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


Triangle* t1 = static_cast<Triangle*>(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 之间的强转,当然也是一样,编译时汇编代码就已经生成好了。

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

聊聊 C++ 中的四种类型转换符的更多相关文章

  1. [转]C++中四种类型转换符的总结

    C++中四种类型转换符的总结 一.reinterpret_cast用法:reinpreter_cast<type-id> (expression)    reinterpret_cast操 ...

  2. C++语言中的四种类型转换

    1 引子 这篇笔记是根据StackOverflow上面的一个问题整理而成,主要内容是对C/C++当中四种类型转换操作进行举例说明.在之前其实对它们都是有所了解的,而随着自己在进行总结,并敲了一些测试示 ...

  3. 聊聊 SpringBoot 中的两种占位符:@*@ 和 ${*}

    前言 在 SpringBoot 项目中,我们经常会使用两种占位符(有时候还会混用),它们分别是: @*@ ${*} 如果我们上网搜索「SpringBoot 的占位符 @」,大部分答案会告诉你,Spri ...

  4. C++中的四种类型转换运算符static_cast、dynamic_cast、const_cast和reinterpret_cast的使用

    1.上一遍讲述了C语言的隐式类型转换和显示类型转换,C语言之所以增加强制类型转换,就是为了强调转换的风险性,但这种强调风险的方式是比较粗放了,粒度比较大,它并没有表明存在什么风险,风险程度如何. 2. ...

  5. C++11中的四种类型转换

    static_cast 基础数据类型转换(基本类型) 同一继承体系中类型的转换(父子类型) 任意类型与空指针(void *)之间的转换(指针类型) dynamic_cast 执行派生类指针或引用与基类 ...

  6. java中的四种修饰符:private、protected、public和default的区别

      本类 本包 子类 包外 public 可以 可以 可以 可以 protected 可以 可以 可以 不可以 default 可以 可以 不可以 不可以 private 可以 不可以 不可以 不可以 ...

  7. C++中的四种类型转换

    //1.常见的类型转换,使用static_cast float f = 1.234; int i =static_cast<int>(f);//等价于 int i = (int)f; // ...

  8. C++中的四种强制类型转换符详解

    阅读目录 C++即支持C风格的类型转换,又有自己风格的类型转换.C风格的转换格式很简单,但是有不少缺点的: 转换太过随意,可以在任意类型之间转换.你可以把一个指向const对象的指针转换成指向非con ...

  9. C++中四种类型转换以及const_cast是否能改变常量的问题

    we have four specific casting operators:dynamic_cast, reinterpret_cast, static_cast and const_cast. ...

随机推荐

  1. ucore lab4 内核线程管理 学习笔记

    越学越简单,真是越学越简单啊 看视频的时候着实被那复杂的函数调用图吓到了.看代码的时候发现条理还是很清晰的,远没有没想象的那么复杂. 这节创建了俩内核线程,然后运行第一个线程,再由第一个切换到第二个. ...

  2. Hadoop(三)通过C#/python实现Hadoop MapReduce

    MapReduce Hadoop中将数据切分成块存在HDFS不同的DataNode中,如果想汇总,按照常规想法就是,移动数据到统计程序:先把数据读取到一个程序中,再进行汇总. 但是HDFS存的数据量非 ...

  3. 将Excel数据转换为Java可识别时间(Date、Timestamp)

    引言 Excel的时间,POI读取到的是double,这个值不是timestamp.需要进行一些转换,将它转换为Date或者Timestamp. Excel中的日期数据: 程序中读取到的数据: 如何转 ...

  4. C++基础-2-引用

    2. 引用 2.1 引用的基本语法 1 #include<iostream> 2 using namespace std; 3 4 5 int main() { 6 7 //引用的基本语法 ...

  5. 浅谈Java-String到底是值传递还是引用传递?

    参数传递 Java 中的参数传递分为 "值传递""引用传递" 如果你学过 C/C++应该很好理解,就是所谓的 "值传递" 和 "指 ...

  6. python数据类型、用户交互和运算符

    基本数据类型 1.字典dict(dictionary) 能够准确的记录储存的信息 """ 大括号里面 放多个元素 之间用逗号隔开 元素为K:V键表示储存 K相对于V一般表 ...

  7. Redisson批量操作类RBuckets和管道利器RBatch

    <Spring Boot 整合Redisson配置篇> <Spring Boot 整合Redisson操作Redis基础篇> <Redisson批量操作类RBuckets ...

  8. Vagrant之CentOS

    Vagrant之CentOS Vagrant官网 https://www.vagrantup.com https://app.vagrantup.com/boxes/search https://ap ...

  9. 146_ACCESS之HR招聘信息管理_64位

    焦棚子的文章目录 点击下载附件 一.背景: 最近把之前做的一个HR招聘信息管理工具翻新了下,有需要的朋友可以自取,主要想解决的问题是多人在跟进人员招聘的时候信息的不对称,这样下来的就可以及时的看到整个 ...

  10. 5分钟快速搭建一个springboot的项目

      现在开发中90%的人都在使用springboot进行开发,你有没有这样的苦恼,如果让你新建一个springboot开发环境的项目,总是很苦恼,需要花费很长时间去调试.今天来分享下如何快速搭建. 一 ...