C/C++中的数据类型转换()/static_cast/dynamic_cast/const_cast/reinterpret_cast

2023-12-04 00:32

本文主要是介绍C/C++中的数据类型转换()/static_cast/dynamic_cast/const_cast/reinterpret_cast,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 前言
  • 数据类型自动转换
  • C语言中的强制类型转换
  • C++中的强制类型转换
    • static_cast
    • dynamic_cast
    • const_cast
    • reinterpret_cast
    • 强转关键字的选择
  • 总结

前言

C/C++属于静态语言,也就是在编译时变量的数据类型即可确定的强类型语言。当不同的数据类型在一起运算或者相互赋值的时候,就需要进行数据类型转换。不同数据类型占用的内存空间不同,而各种数据类型的转换时有规则的,一种通用的规则就是“小转大”自动进行,“大转小”需要强制执行。这里的“大”和“小”指的是数据范围。

为什么会有数据范围大小的区别呢?这就和饭店里的盘子一样,不同的菜肴通常使用不同的盘子,盘子有大有小,如果把小盘子里的菜装到大盘子里没有问题,但是把大盘子里的菜放到小盘子中就会溢出来,假设都使用大盘子就不会产生溢出的问题,但是这样会产生空间的浪费。而C/C++中不同类型的变量占用的内存空间不同与这些盘子非常相似,当范围小的变量赋值给范围大的变量时没有问题,但是反过来也会出现溢出。

数据类型自动转换

当不同类型的变量同时运算时就会发生数据类型的自动转换,以常见的 charshortintlongfloatdouble 这些类型为例,如果 charint 两个类型的变量相加时,就会把 char 先转换成 int 再进行加法运算,如果是 intdouble 类型的变量相乘就会把 int 转换成 double 再进行运算。

自动转换的行为如下图所示,方向是从左往右自动进行:

char
unsigned/int
short
unsigned/long
double
float

C语言中的强制类型转换

前面说了自动转换,从这里开始聊聊强制类型转换,需要强制类型转换往往程序不那么智能了,需要人工进行干预。比如把一个int 类型的变量赋值给 char 类型的变量,或者说把两个 int 相乘时可能会得到一个很大的数,所以需要先把 int 强制转换成 double 计算防止溢出。

强制类型转换的格式为:(new_type_name) expression,其中 new_type_name 为新类型名称,expression为表达式。例如:


int val = 65535;
char ch = (char)val;

或者

int m = 2147483647, n = 100;
double result = (double)m * n;

无论是自动的类型转换还是强制类型转换,都只是为了本次操作或运算而进行的临时转换,转换的结果也会保存到临时的内存空间内,不会改变数据本来的类型或者具体的值。

有些强制类型转换是对原有数据的重新解释,比如:

void test(void* p)
{char* buffer = (char*)p;// ...
}

void* 类型的变量p,经过强制类型转换以后变成了char类型的指针,此后就可以把这段内存空间当成字符数组来处理了。

C++中的强制类型转换

在C++语言中新增了四个用于强制类型转换的关键字,分别是 static_castdynamic_cast, const_cast、 和 reinterpret_cast,使用语法为 xxxx_cast<new_type_name>(expression)

相比于C语言中使用小括号()来完成强制类型转换,C++中这几个关键字的引入能更清晰的表明它要完成强制类型转换的意图,容易暴露出隐藏的问题。

其实很长一段时间以来,我对于这四种强转方式区分的不是很清晰,其中 const_cast 的功能还比较容易辨别,但是另外3种经常混作一团,所以才有了这篇总结,而仔细学习后才发现,这4种强转关键字的区别就在他们的名字上,下面逐个来看一下。

static_cast

这个关键字的作用主要表现在 static 上,是一种静态的转换,在编译期就能确定的转换,可以完成C语言中的强制类型转换中的大部分工作,但需要注意的是,它不能转换掉表达式的 constvolitale 或者 __unaligned 属性。

它主要有以下几种用法:

  1. 用于基本数据类型之间的转换,如把int转换成char,把int转换成double等。
    int val = 110119;char c = static_cast<char>(val);double d = static_cast<double>(val);
  1. 将表达式转换成void类型,并将转换后的结果丢弃
    int val = 110119;static_cast<void>(val);
  1. 可以用于void* 和其他指针类类型之间的转换,但是不能用于两个无关指针类型的直接转换
   // 正常转换int *p = new int;void* p1 = static_cast<void*>(p);char* p2 =  static_cast<char*>(p1);// 编译失败 //error: invalid static_cast from type ‘int*’ to type ‘char*’char* p3 =  static_cast<char*>(p);
  1. 可以用于类继承结构中基类和派生类之间指针或引用的转换,向上转型安全,向下转型由于没有动态类型检查,是不安全的。
   struct B { };struct D : B { };D d;B& rb = d;D& rd = static_cast<D&>(rb);
  1. 如果涉及左值到右值、数组到指针或函数到指针的转换,也可以通过static_cast显式执行。
   template<typename _Tp>inline typename std::remove_reference<_Tp>::type&&move(_Tp&& __t){ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

dynamic_cast

从名字上看,这个关键字与 static_cast 的静态转换是对立的,这是一个“动态”转换函数,只能对指针和引用的进行转换,并且只用于类继承结构中基类和派生类之间指针或引用的转换,可以进行向上、向下,或者横向的转换。

相比于 static_cast 的编译时转换, dynamic_cast 的转换还会在运行时进行类型检查,转换的条件也比较苛刻,必须有继承关系的类之间才能转换,并且在基类中有虚函数才可以,有一种特殊的情况就是可以把类指针转换成 void* 类型。

关于使用中的常见问题,参考以下几种情况:

  1. 普通类型的指针无法转换
   int val = 100;int *p = &val;// 编译失败 //error: cannot dynamic_cast ‘p’ (of type ‘int*’) to type ‘char*’ (target is not pointer or reference to class)char* pc =  dynamic_cast<char*>(p);
  1. 继承结构中基类里面没有虚函数无法转换
   struct B { };struct D : B { };D d;B* pb = &d;// 编译失败 //error: cannot dynamic_cast ‘pb’ (of type ‘struct test1()::B*’) to type ‘struct test1()::D*’ (source type is not polymorphic)D* pd = dynamic_cast<D*>(pb)
  1. 指针或引用转换的类型不是正确的类型,如果参数类型是指针会返回目标类型空指针,如果参数类型是引用则会抛出 std::bad_cast 异常。
   struct B { virtual void test() {} };struct D : B { };B d;B* pb = &d;D* pd = dynamic_cast<D*>(pb);// 编译成功,但是pb指针指向的类型是 B,向下转型失败,输出结果是0,也就是空指针std::cout << pd << std::endl;
  1. 一个正常转换的例子,包含向上、向下、横向转换
   struct B { virtual void test() {} };struct D1 : virtual B { };struct D2 : virtual B { };struct MD : D1, D2 { };D1* pd1 = new MD();std::cout << pd1 << std::endl;// 向上转型B* pb = dynamic_cast<B*>(pd1);std::cout << pb << std::endl;// 向下转型MD* pmd = dynamic_cast<MD*>(pd1);std::cout << pmd << std::endl;// 横向转型D2* pd2 = dynamic_cast<D2*>(pd1);std::cout << pd2 << std::endl;

运行结果如下,在横向转换时指针发生了变化,可以看出 dynamic_cast 不是简单的数据强转,还进行了指针的偏移:

albert@home-pc:/mnt/d/testconvert$ g++ cppconvert.cpp
albert@home-pc:/mnt/d/testconvert$ ./a.out
0x15c0c40
0x15c0c40
0x15c0c40
0x15c0c48

const_cast

在C/C++中,const限定符通常被用来限定变量,用于表示该变量的值不能被修改,这种限定可以避免程序员犯一些初级错误,但同时也造成了一些不便,比如一些已有函数要求非常量指针,但是掉用这些函数的接口函数中都传递了常量指针,这时候就要对指针类型去常量化。

但需要特别注意的是 const_cast 不能去除变量的常量性,只能用来去除指向常数对象的指针或引用的常量性,且去除常量性的对象必须为指针或引用。

常量指针被转化成非常量指针,并且仍然指向原来的对象,常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象可能被转换成非常量对象。

  1. 尝试去除非指针和引用的类型的常量性会编译失败
   const int i = 6;// 编译错误 //int j = const_cast<int>(i);
  1. 企图用一个指针来修改常量:
    const int val = 6;//编译错误 //error: invalid conversion from ‘const int*’ to ‘int*’ [-fpermissive]int* cp = &val;
  1. 修改一个指针的常量性:
   const int val = 6;std::cout << "&val=" << &val << ", val=" << val << std::endl;const int* cp = &val;int *p = const_cast<int*>(cp);*p = 2;std::cout << "&val=" << &val << ", val=" << val << std::endl;std::cout << "p=" << p << ", *p=" << *p << std::endl;

运行结果如下:

&val=0x7ffff7446bd4, val=6
&val=0x7ffff7446bd4, val=6
p=0x7ffff7446bd4, *p=2

运行之后,变量 p 指向了变量val地址,并改变了地址所指向的内存数据,但是打印 val 的值并没有发生变化,这是因为 val 作为常量在编译期使用它的地方就进行了替换,接下来再看另一种情况。

   int init = 6;const int val = init;std::cout << "&val=" << &val << ", val=" << val << std::endl;const int* cp = &val;int *p = const_cast<int*>(cp);*p = 2;std::cout << "&val=" << &val << ", val=" << val << std::endl;std::cout << "p=" << p << ", *p=" << *p << std::endl;

代码逻辑不变,只在开始的位置使用 init 这个变量来代替 6 这个常数,运行结果如下:

val=0x7fffe8c71fa0, val=6
&val=0x7fffe8c71fa0, val=2
p=0x7fffe8c71fa0, *p=2

运行之后 val 本身的变化也应用到了使用它的地方,这里的编译器替换已经不起作用了。

实际上,使用const_cast通常是一种无奈之举,利用const_cast去掉指针或引用的常量性并且去修改原始变量的数值,这是一种非常不好的行为,如果可以的话,尽可能在程序设计阶段就规避这种情况。

reinterpret_cast

它被用于不同类型指针或引用之间的转换,或者指针和整数之间的转换,是对比特位的简单拷贝并重新解释,因此在使用过程中需要特别谨慎,比如前面提到的一个例子,static_cast 不能将 int* 直接强转成 char*,使用reinterpret_cast就可以办到。

  1. 不同基础类型指针类型之间转换:
   int *p = new int;// 编译失败 //error: invalid static_cast from type ‘int*’ to type ‘char*’char* p1 =  static_cast<char*>(p);// 编译成功char* p2 =  reinterpret_cast<char*>(p1);
  1. 基础类型指针与类对象指针之间的转换:
   struct B { int val;};B b{100};std::cout << "b.val=" << b.val << std::endl;int* p = reinterpret_cast<int*>(&b);std::cout << "*p=" << *p << std::endl;

运行之后可以得到 *p 的值为100,也就是重新解释了变量 b 的地址为整型指针。

  1. 将地址值转换成整数
   struct B { int val;};B b{101};std::cout << "&b=" << &b << std::endl;long addr = reinterpret_cast<long>(&b);std::cout << "addr=" << addr << std::endl;

运行结果如下:

&b=0x7ffffdc4f270
addr=140737450930800

这里的地址 0x7ffffdc4f270 被解释成了整数 140737450930800,因为涉及到字节序,这也是很多文章提到的 reinterpret_cast 不具备一致性的问题,我们需要知道这一个点,只要代码不依赖主机字节序就没有问题。

强转关键字的选择

好几个关键字,并且有些功能还是重复的,那么究竟该选哪一个呢?这个真得按照经验来选,我建议使用排除法,按照 const_cast -> dynamic_cast -> reinterpret_cast -> static_cast 的顺序带入选择。

  1. 先看是不是要去掉指针或引用的常量属性,如果是只能选择 const_cast

  2. 再看转换的是不是继承体系下的多态结构,如果是这种结构下的指针和引用的转换最好使用 dynamic_cast

  3. 接着看是不是偏底层的代码,需要将无关类型指针进行转换,或者指针与整数之间进行转换,如果是则选择 reinterpret_cast

  4. 前三种情况都不满足,那就只能使用 static_cast

总结

  • C/C++中不同数据类型进行运算或者赋值的时候会发生数据转换,这种转换有些是自动进行的,有些需要进行显示的强制类型转换
  • 在C语言中强制类型转换写成(new_type_name) expression的形式,new_type_name 是要转换的目标类型,expression 是待转换的表达式
  • 在C++中强制类型转换通过更明显的关键字来完成,分别是static_castdynamic_cast, const_cast、 和 reinterpret_cast
  • static_cast 是静态转换,在编译期完成完成转换,与C语言中的强制类型转换重合度最高
  • dynamic_cast 是动态转换,在运行时转换会进行检查,必须用在有继承关系的多态结构中
  • const_cast 是常量转换,用于取出指针或引用的常量属性,但是尽量通过设计杜绝它的使用场景
  • reinterpret_cast 是一种内存数据的重新解释,比较原始,开发者使用它的时候应该明确的知道自己在做什么

==>> 反爬链接,请勿点击,原地爆炸,概不负责!<<==

今夜的雨,好美~

这篇关于C/C++中的数据类型转换()/static_cast/dynamic_cast/const_cast/reinterpret_cast的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/451445

相关文章

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

Redis的数据过期策略和数据淘汰策略

《Redis的数据过期策略和数据淘汰策略》本文主要介绍了Redis的数据过期策略和数据淘汰策略,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录一、数据过期策略1、惰性删除2、定期删除二、数据淘汰策略1、数据淘汰策略概念2、8种数据淘汰策略

轻松上手MYSQL之JSON函数实现高效数据查询与操作

《轻松上手MYSQL之JSON函数实现高效数据查询与操作》:本文主要介绍轻松上手MYSQL之JSON函数实现高效数据查询与操作的相关资料,MySQL提供了多个JSON函数,用于处理和查询JSON数... 目录一、jsON_EXTRACT 提取指定数据二、JSON_UNQUOTE 取消双引号三、JSON_KE

c++中std::placeholders的使用方法

《c++中std::placeholders的使用方法》std::placeholders是C++标准库中的一个工具,用于在函数对象绑定时创建占位符,本文就来详细的介绍一下,具有一定的参考价值,感兴... 目录1. 基本概念2. 使用场景3. 示例示例 1:部分参数绑定示例 2:参数重排序4. 注意事项5.

使用C++将处理后的信号保存为PNG和TIFF格式

《使用C++将处理后的信号保存为PNG和TIFF格式》在信号处理领域,我们常常需要将处理结果以图像的形式保存下来,方便后续分析和展示,C++提供了多种库来处理图像数据,本文将介绍如何使用stb_ima... 目录1. PNG格式保存使用stb_imagephp_write库1.1 安装和包含库1.2 代码解

Python给Excel写入数据的四种方法小结

《Python给Excel写入数据的四种方法小结》本文主要介绍了Python给Excel写入数据的四种方法小结,包含openpyxl库、xlsxwriter库、pandas库和win32com库,具有... 目录1. 使用 openpyxl 库2. 使用 xlsxwriter 库3. 使用 pandas 库

SpringBoot定制JSON响应数据的实现

《SpringBoot定制JSON响应数据的实现》本文主要介绍了SpringBoot定制JSON响应数据的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们... 目录前言一、如何使用@jsonView这个注解?二、应用场景三、实战案例注解方式编程方式总结 前言

C++实现封装的顺序表的操作与实践

《C++实现封装的顺序表的操作与实践》在程序设计中,顺序表是一种常见的线性数据结构,通常用于存储具有固定顺序的元素,与链表不同,顺序表中的元素是连续存储的,因此访问速度较快,但插入和删除操作的效率可能... 目录一、顺序表的基本概念二、顺序表类的设计1. 顺序表类的成员变量2. 构造函数和析构函数三、顺序表