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

c/c++数据类型转换.


author: hjjdebug
date: 2025年 05月 18日 星期日 20:28:52 CST
descrip: c/c++数据类型转换.


文章目录

  • 1. 为什么需要类型转换?
    • 1.1 发生的时机:
    • 1.2 常见的发生转换的类型:
  • 2. c语言的类型转换: (Type) value
    • 2.1 c语言的类型变换是如何实现的? 规则是什么?
  • 3. c++ 的static_cast
  • 4. c++的reinterpret_cast
  • 5. dynymic_cast 的使用.
    • 5.1: 测试代码
    • 5.2 statci_cast 处理父子类对象地址
    • 5.3 动态变换: dynamic_cast 处理父子类对象地址
    • 5.4 为什么指针类型老是变来变去的,是什么类型就是什么类型,不变不好吗?
    • 5.4 为什么要用 dynamic_cast ?

本来数据是什么类型就是什么类型,一般是不需要转换的,
但某些特殊情况下会发生转换.

1. 为什么需要类型转换?

因为类型不匹配,所以要进行类型转换.

1.1 发生的时机:

  1. 当进行赋值和运算时有可能发生数据转换.
  2. 进行函数调用时,有可能会发生数据类型转换.
    有可能是说类型不匹配时,会按照一定规则进行变换,如果不是自定义的规则,
    那就是编译器隐含的规则.

1.2 常见的发生转换的类型:

  1. 整形变成浮点型,浮点型变成整形.
  2. 子类指针退化为父类(上变换)和父类指针变换为子类(下变换)
  3. 自定义的类型变换规则.

2. c语言的类型转换: (Type) value

举例:

$ cat main.cpp
#include <stdio.h>
int main()
{int i = 1;double d = i; // 隐式类型转换printf("i:%d, d:%.2f\n",i,d);int* ptr = &i;long address = (long)ptr; // 显示的强制类型转换printf("ptr:%p, address:%ld\n",ptr,address);return 0;
}

执行:
$ ./Test
i:1, d:1.00
ptr:0x7ffca9a5b0bc, addr:140723154694332,addr:0x7ffca9a5b0bc

2.1 c语言的类型变换是如何实现的? 规则是什么?

4 int i = 1;
0x00005555555546c1 <+23>: movl $0x1,-0x24(%rbp) //把1赋值给变量i
5 double d = i; // 隐式类型转换
0x00005555555546c8 <+30>: mov -0x24(%rbp),%eax //取到数值i
0x00005555555546cb <+33>: cvtsi2sd %eax,%xmm0 //变成浮点数
0x00005555555546cf <+37>: movsd %xmm0,-0x20(%rbp) //保存数值到d变量

7 int* ptr = &i;
0x00005555555546f7 <+77>: lea -0x24(%rbp),%rax //取到i的地址
0x00005555555546fb <+81>: mov %rax,-0x18(%rbp) //把地址保存到ptr变量
8 long address = (long)ptr; // 显示的强制类型转换
0x00005555555546ff <+85>: mov -0x18(%rbp),%rax //从ptr地址中取到地址(解引用的意思)
0x0000555555554703 <+89>: mov %rax,-0x10(%rbp) //把地址保存到address 变量
可见地址类型就天然等于long 类型, 它们数值时完全相等的.

c语言会按照它默认规则进行转换,例如把long 转为 int 它也给转,当正确性你自己保证
c++认为c的强制类型转换(type)value 方式太过强暴, 将之分解为4重转换类型,
static_cast, 静态转换
dynymic_cast, 动态转换
reinterpreted_cast, 再解释转换
const_cast, 常变换.

3. c++ 的static_cast

测试代码:

#include <iostream>
using namespace std;
int main()
{double d = 1.23;int a = static_cast<int>(d);cout << a << endl;int* p = &a;
//    int address = static_cast <int>(p);  //error: invalid static_cast from type ‘int*’ to type ‘int’
//    long address = static_cast <long>(p);  //error: invalid static_cast from type ‘int*’ to type ‘long int’long address = reinterpret_cast <long>(p);  //error: invalid static_cast from type ‘int*’ to type ‘long int’cout << address << endl;return 0;
}

我们看到static_cast 把浮点数到int的变换与c下的转换时完全一致的.
汇编代码如下:
5 double d = 1.23;
=> 0x0000555555554892 <+8>: movsd 0x136(%rip),%xmm0 # 0x5555555549d0
0x000055555555489a <+16>: movsd %xmm0,-0x8(%rbp)

6 int a = static_cast(d);
0x000055555555489f <+21>: movsd -0x8(%rbp),%xmm0
0x00005555555548a4 <+26>: cvttsd2si %xmm0,%eax
0x00005555555548a8 <+30>: mov %eax,-0xc(%rbp)
但它拒绝将整形地址向整形变量转换,也拒绝向long 整形变量转换. 编译会直接给出错误.
可见对于static_cast, 它的转换实现与c下的强制转换是一样的.
但对于高风险的转换,它不干了.

如果你确实需要把指针变成整数怎么办,那你可以用另一个关键字reinterpret_cast, 它的要求
会放宽,允许你转换,不过后果自负喽!, 你自己负责使用时的正确性.

4. c++的reinterpret_cast

从上面的例子可以看出,static_cast 对风险高的转换它不干了,让reinterpret_cast来承担风险. reinterpret_cast 的转换方式跟c下的强制转换是一样的.

5. dynymic_cast 的使用.

其主要是为了完成指针类型的上变换(辈份变大了和下变换辈分变小了)
也可能辈分没有变,但从一个基类指针变到了另一个基类指针.
c++的类和继承关系, 一个形象的理解是一个类就对应一个椭圆,子类继承了父类,那就是一个大椭圆套住了一个小椭圆,
继承了2个基类,那就是套住了2个小椭圆.
static_cast 可以处理这种有继承关系的对象指针的转换.
dynymic_cast 也可以处理这种有继承关系的对象指针的转换. 但它是动态的,支持运行时监测.
完成这种转换是需要类型信息的, 下面举例说明转换是怎样进行的,

5.1: 测试代码

$ cat main.cpp
#include <iostream>
using namespace std;
// 假设有2个基类 Base1和Base2 和一个派生类 Derived,各类型指针转换会是什么?
class Base1
{public:// error: cannot dynamic_cast ‘p’ (of type ‘class Base*’) to type ‘class Derived*’ (source type is not polymorphic)// 必需要有一个虚函数,否则编译错误说类型不具有多态性.virtual void foo() {cout<<"from Base1::foo\n";}
};
class Base2
{public:virtual void bar() {cout<<"from Base2::bar\n";}
};class Derived : public Base1, public Base2
{public:void compose() { cout<< "from Derived:compose\n";}
};
int main()
{Derived * pd = new Derived();// 用基类指针,指向一个派生类对象Base1* p1 = pd;Base2* p2 = pd;//当你看到p1,p2的不同,不要奇怪,这里有隐含指针变换cout<<"p1:"<<p1<<",p2:"<<p2<<",pd:"<<pd<<endl;// 使用 dynamic_cast 将 p1 转换为派生类指针 q, 下变换// 能变换吗? 能,因为p1 本来就是派生类对象的指针.//  如果p1 是用 new Base() 创建出来的, 那就转不成继承类指针了!// 转不成了变换的值是nullptr.// dynamic_cast<Derived *> 可看成是一个编译器生成的内置函数// 变化比较复杂. 有对指针的调整. 需要RTTI 运行时类型信息支持.Derived* q1 = dynamic_cast<Derived*>(p1);cout<<"p1:"<<p1<<",q1:"<<q1<<endl; //p1 和q1 是相等的.Derived* q2 = dynamic_cast<Derived*>(p2);//当你看到转出的q1,q2竟然相等,也不用奇怪,因为它们就是一个对象cout<<"q1:"<<q1<<",q2:"<<q2<<",p1:"<<p1<<",p2"<<p2<<endl;//	甚至从Base1 也能导出Base2指针,都是一个对象,它们是可以互相推导计算的.// 推导计算的法则是根据RTTI信息,计算就是加一个偏移,减一个偏移的事情.// 如果转换成功,q1 不为空,可以调用派生类的成员函数 compose()if (q1){q1->compose();}else{cout << "down convert failed\n";}return 0;
}

5.2 statci_cast 处理父子类对象地址

继承类指针向基类指针的隐含转换.
24 // 用基类指针,指向一个派生类对象
25 Base1* p1 = pd;
0x00000000004008f8 <+49>: mov -0x38(%rbp),%rax
0x00000000004008fc <+53>: mov %rax,-0x30(%rbp) //直接赋值

26 Base2* p2 = pd;
0x0000000000400900 <+57>: cmpq $0x0,-0x38(%rbp) //pd==0? 付给0
0x0000000000400905 <+62>: je 0x400911 <main()+74>
0x0000000000400907 <+64>: mov -0x38(%rbp),%rax
0x000000000040090b <+68>: add $0x8,%rax //把值加上8,付给p2
0x000000000040090f <+72>: jmp 0x400916 <main()+79>
0x0000000000400911 <+74>: mov $0x0,%eax
0x0000000000400916 <+79>: mov %rax,-0x28(%rbp)

36 Derived* q1 = static_cast<Derived*>(p1); //静态变换,也叫编译期变换
0x0000000000400999 <+210>: mov -0x30(%rbp),%rax
0x000000000040099d <+214>: mov %rax,-0x20(%rbp) // p1直接付给了q1

// p2付q2, 它就改一改算法, 你说gcc多聪明啊! 它了解意图.
38 Derived* q2 = static_cast<Derived*>(p2);
0x00000000004009ff <+312>: cmpq $0x0,-0x28(%rbp) // p2==0? 付给0
0x0000000000400a04 <+317>: je 0x400a10 <main()+329>
0x0000000000400a06 <+319>: mov -0x28(%rbp),%rax
0x0000000000400a0a <+323>: sub $0x8,%rax // p2减去8,付给q2
0x0000000000400a0e <+327>: jmp 0x400a15 <main()+334>
0x0000000000400a10 <+329>: mov $0x0,%eax
0x0000000000400a15 <+334>: mov %rax,-0x18(%rbp)

5.3 动态变换: dynamic_cast 处理父子类对象地址

36 Derived* q1 = dynamic_cast<Derived*>(p1);
0x00000000004009e9 <+210>: mov -0x30(%rbp),%rax
0x00000000004009ed <+214>: test %rax,%rax
0x00000000004009f0 <+217>: je 0x400a0f <main()+248> //p1==0? 付给0
0x00000000004009f2 <+219>: mov $0x0,%ecx //第4参数,0,偏移数据
0x00000000004009f7 <+224>: mov $0x601da0,%rdx //第3参数,类型信息
0x00000000004009fe <+231>: mov $0x601de8,%rsi //第2参数,类型信息
0x0000000000400a05 <+238>: mov %rax,%rdi // 第一参数p1
0x0000000000400a08 <+241>: callq 0x400810 __dynamic_cast@plt//调用函数
0x0000000000400a0d <+246>: jmp 0x400a14 <main()+253>
0x0000000000400a0f <+248>: mov $0x0,%eax
0x0000000000400a14 <+253>: mov %rax,-0x20(%rbp) //返回值给q1

38 Derived* q2 = dynamic_cast<Derived*>(p2);
0x0000000000400a76 <+351>: mov -0x28(%rbp),%rax
0x0000000000400a7a <+355>: test %rax,%rax
0x0000000000400a7d <+358>: je 0x400a9c <main()+389>
0x0000000000400a7f <+360>: mov $0x8,%ecx //第4参数,8,偏移数据
0x0000000000400a84 <+365>: mov $0x601da0,%rdx //第3参数,类型信息
0x0000000000400a8b <+372>: mov $0x601dd8,%rsi //第2参数,类型信息
0x0000000000400a92 <+379>: mov %rax,%rdi /// 第一参数p2
0x0000000000400a95 <+382>: callq 0x400810 __dynamic_cast@plt
0x0000000000400a9a <+387>: jmp 0x400aa1 <main()+394>
0x0000000000400a9c <+389>: mov $0x0,%eax
0x0000000000400aa1 <+394>: mov %rax,-0x18(%rbp)//返回值给q2
注意: -0x18(%rbp)与-0x20(%rbp)是相差8个byte 而不是2个byte, 一走神有点犯晕.
刚好存储长整形地址.

5.4 为什么指针类型老是变来变去的,是什么类型就是什么类型,不变不好吗?

不变确实挺好,能不变就不要变.
但是c++的2大要点是继承和多态. 另一大要点是封装这就不说了.
就是例子中一个类继承了2个类,本来你用导出类指针是什么都能访问到的.
但是,如果有一个函数,它要求你传入基类的地址而不是导出类地址,这就需要转换了.
把地址调一调,才能访问到正确的数据.
用基类地址还能访问到继承类的函数(虚函数),这就是多态.

运行结果:
$ ./Test
p1:0x16b6e70,p2:0x16b6e78,pd:0x16b6e70
p1:0x16b6e70,q1:0x16b6e70
q1:0x16b6e70,q2:0x16b6e70,p1:0x16b6e70,p20x16b6e78
from Derived:compose

对运行结果的解释
$ ./Test
//p1和p2是不等的(从一个导出类对象地址转换成的两个基类地址是不等的),
//p1等于pd(其中一个基类地址跟导出类地址相等)
p1:0x16b6e70,p2:0x16b6e78,pd:0x16b6e70
//这是逆变换, 转换出的导出类与基类地址相同,但另一个导出类和基类地址不同
p1:0x16b6e70,q1:0x16b6e70
//2个基类转换出了相同的导出类地址q1,q2, 这说明它们是一个对象
q1:0x16b6e70,q2:0x16b6e70,p1:0x16b6e70,p20x16b6e78
from Derived:compose

5.4 为什么要用 dynamic_cast ?

从上边例子中我们看出, static_cast 直接计算了偏移,很简洁, 而dynamic_cast
还要去调用函数__dynamic_cast@plt 才能得到地址,显然代价更高.
那为什么还要用dynamic_cast, 全部改为static_cast 不好吗?
上边的例子是用不着dynamic_cast, 但是, 这里有但是…

我写了一个函数, 传来的是基类指针, 我不知道它真实身份是那种子类,就需要动态指针变换,由此判定它是什么类型. 这就是运行期判断,

测试代码:

#include <iostream>
using namespace std;
class Base {virtual void foo(){}
};
class Derived1 : public Base
{
};class Derived2 : public Base
{
};void deal_it(Base *b)
{auto d1 = dynamic_cast<Derived1 *>(b);
//	auto d1 = static_cast<Derived1 *>(b);if(d1){// 处理Derived1类型                                                 cout<<"is Derived1 type\n";return;}auto d2 = dynamic_cast<Derived2 *>(b);
//	auto d2 = static_cast<Derived2 *>(b);if(d2){// 处理Derived2类型                                                 cout<<"is Derived2 type\n";return;}
}
int main()
{auto b = new Derived2();deal_it(b);return 0;
}

测试结果:
$ ./Test
is Derived2 type

如果你将dynamic_cast改称static_cast, 那就得不到正确结果了, 因为static_cast 不管三七二十一,把指针加个偏移就返回了.
这样转换的指针不为0,就永远只走第一条了.
至于为什么dynamic_cast 能正确工作, 就不再这里说明了.
自定义的类型变换就不在本博举例了,可参考其它博客.

相关文章:

  • 二:操作系统之进程控制块(PCB)
  • Selinux权限问题处理指导文档分享
  • 菱形继承原理
  • 中国与全球电子取证行业市场报告(公开信息版)
  • 暴雨大讲堂:高性能计算面临的芯片挑战
  • 牛客网NC210769: 字母大小写转换问题解析
  • HJ5 进制转换【牛客网】
  • Python 中二维列表(list)(嵌套列表)详解
  • uWSGI是什么?
  • Java中关于方法的调用和递归
  • 【cursor】有效解决
  • Appium自动化测试环境搭建及配置
  • 西门子1200/1500博图(TIA Portal)寻址方式详解
  • AI 制作游戏美术素材流程分享(程序员方向粗糙版)
  • CCpro工程编程软件
  • Git从入门到精通
  • centos7.9扩展已有分区空间
  • [LevelDB]LevelDB版本管理的黑魔法-为什么能在不锁表的情况下管理数据?
  • bus hound抓取的数据包各字段含义解释
  • DAY26 函数定义与参数
  • 北美票房|华纳又赢了,《死神来了6》开画远超预期
  • 荷兰外交大臣费尔德坎普将访华
  • 著名文学评论家、原伊犁师范学院院长吴孝成逝世
  • 美国前总统拜登确诊前列腺癌
  • 福建、广西等地有大暴雨,国家防总启动防汛四级应急响应
  • 复旦一校友捐赠1亿元,却不留名