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

news2026/2/19 16:45:31

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 能正确工作, 就不再这里说明了.
自定义的类型变换就不在本博举例了,可参考其它博客.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2379569.html

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

相关文章

Django 项目的 models 目录中,__init__.py 文件的作用

在 Django 项目的models/init.py文件中&#xff0c;这些导入语句的主要作用是将各个模型类从不同的模块中导入到models包的命名空间中。这样做有以下几个目的&#xff1a; 简化导入路径 当你需要在项目的其他地方使用这些模型时&#xff0c;可以直接从models包导入&#xff0c…

网络的知识的一些概念

1.什么是局域网&#xff0c;什么是广域网 局域网&#xff08;Local area network&#xff09;也可以称为本地网&#xff0c;内网&#xff0c;局域网有这几个发展经历&#xff1a; 最开始电脑与电之间是直接用网线连接的 再后来有了集线器&#xff08;&#xff09; 再后来出…

芋道项目,商城模块数据表结构

一、需求 最近公司有新的业务需求&#xff0c;调研了一下&#xff0c;决定使用芋道&#xff08;yudao-cloud&#xff09;框架,于是从github&#xff08;https://github.com/YunaiV/yudao-cloud&#xff09;上克隆项目&#xff0c;选用的是jdk17版本的。根据项目启动手册&#…

【氮化镓】HfO2钝化优化GaN 器件性能

2025年,南洋理工大学的Pradip Dalapati等人在《Applied Surface Science》期刊发表了题为《Role of ex-situ HfO2 passivation to improve device performance and suppress X-ray-induced degradation characteristics of in-situ Si3N4/AlN/GaN MIS-HEMTs》的文章。该研究基…

SQLMesh 增量模型从入门到精通:5步实现高效数据处理

本文深入解析 SQLMesh 中的增量时间范围模型&#xff0c;介绍其核心原理、配置方法及高级特性。通过实际案例说明如何利用该模型提升数据加载效率&#xff0c;降低计算资源消耗&#xff0c;并提供配置示例与最佳实践建议&#xff0c;帮助读者在实际项目中有效应用这一强大功能。…

Zookeeper 入门(二)

4. Zookeeper 的 ACL 权限控制( Access Control List ) Zookeeper 的ACL 权限控制,可以控制节点的读写操作,保证数据的安全性&#xff0c;Zookeeper ACL 权 限设置分为 3 部分组成&#xff0c;分别是&#xff1a;权限模式&#xff08;Scheme&#xff09;、授权对象&#xff08…

【架构篇】安全架构-双向认证

mTLS&#xff08;Mutual TLS&#xff09;详解&#xff1a;双向认证的原理、流程与实践 摘要 mTLS&#xff08;Mutual TLS&#xff09;是一种基于数字证书的双向身份验证协议&#xff0c;广泛应用于微服务通信、金融交易等高安全场景。本文深入解析mTLS的工作原理、认证流程、W…

负载均衡—会话保持技术详解

一、会话保持的定义 会话保持&#xff08;Session Persistence&#xff09;是一种负载均衡策略&#xff0c;其核心机制是确保来自同一客户端的连续请求&#xff0c;在特定周期内被定向到同一台后端服务器进行处理。这种机制通过记录和识别客户端的特定标识信息&#xff0c;打破…

Flask快速入门和问答项目源码

Flask基础入门 源码&#xff1a; gitee&#xff1a;我爱白米饭/Flask问答项目 - 码云 目录 1.安装环境2.【debug、host、port】3.【路由params和query】4.【模板】5.【静态文件】6.【数据库连接】6.1.安装模块6.2.创建数据库并测试连接6.3.创建数据表6.4.ORM增删改查 6.5.ORM模…

go语法大赏

前些日子单机房稳定性下降&#xff0c;找了好一会才找到真正的原因。这里面涉及到不少go语法细节&#xff0c;正好大家一起看一下。 一、仿真代码 这是仿真之后的代码 package mainimport ("fmt""go.uber.org/atomic""time" )type StopSignal…

软件工程各种图总结

目录 1.数据流图 2.N-S盒图 3.程序流程图 4.UML图 UML用例图 UML状态图 UML时序图 5.E-R图 首先要先了解整个软件生命周期&#xff1a; 通常包含以下五个阶段&#xff1a;需求分析-》设计-》编码 -》测试-》运行和维护。 软件工程中应用到的图全部有&#xff1a;系统…

AAAI2024 | 基于特征多样性对抗扰动攻击 Transformer 模型

Attacking Transformers with Feature Diversity Adversarial Perturbation 摘要-Abstract引言-Introduction相关工作-Related Work方法-Methodology实验-Experiments结论-Conclusion 论文链接 本文 “Attacking Transformers with Feature Diversity Adversarial Perturbatio…

关于数据湖和数据仓的一些概念

一、前言 随着各行业数字化发展的深化,数据资产和数据价值已越来越被深入企业重要发展的战略重心,海量数据已成为多数企业生产实际面临的重要问题,无论存储容量还是成本,可靠性都成为考验企业数据治理的考验。本文来看下海量数据存储的数据湖和数据仓,数据仓库和数据湖,…

常用的Java工具库

1. Collections 首先是 java.util 包下的 Collections 类。这个类主要用于操作集合&#xff0c;我个人非常喜欢使用它。以下是一些常用功能&#xff1a; 1.1 排序 在工作中&#xff0c;经常需要对集合进行排序。让我们看看如何使用 Collections 工具实现升序和降序排列&…

R S的EMI接收机面板

图片摘自R & S官网。 根据您提供的第一张图&#xff08;设备前面板带屏幕的图像&#xff09;&#xff0c;这是 Rohde & Schwarz ESRP7 EMI Test Receiver 的正面显示界面&#xff0c;我将对屏幕上显示的参数逐项进行解读&#xff1a; &#x1f5a5;️ 屏幕参数解读 左…

[ctfshow web入门] web122

信息收集 这一题把HOME开放了&#xff0c;把#和PWD给过滤了 <?php error_reporting(0); highlight_file(__FILE__); if(isset($_POST[code])){$code$_POST[code];if(!preg_match(/\x09|\x0a|[a-z]|[0-9]|FLAG|PATH|BASH|PWD|HISTIGNORE|HISTFILESIZE|HISTFILE|HISTCMD|US…

Java虚拟机 - JVM与Java体系结构

Java虚拟机 JVM与Java体系结构为什么要学习JVMJava与JVM简介Java 语言的核心特性JVM&#xff1a;Java 生态的基石JVM的架构模型基于栈的指令集架构&#xff08;Stack-Based&#xff09;基于寄存器的指令集架构&#xff08;Register-Based&#xff09;JVM生命周期 总结 JVM与Jav…

灌区量测水自动化监测解决方案

一、方案背景 随着社会发展和人口增长&#xff0c;水资源需求不断增大。我国水资源总量虽然丰富&#xff0c;但时空分布不均&#xff0c;加之农业用水占比大且效率偏低&#xff0c;使得水资源短缺问题日益凸显。农业用水一直是我国的耗水大户&#xff0c;占全部耗水总量的60%以…

界面控件DevExpress WinForms v24.2 - 数据处理功能增强

DevExpress WinForms拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForms能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜…

Linux的MySQL头文件和找不到头文件问题解决

头文件 #include <iostream> #include <mysql_driver.h> #include <mysql_connection.h> #include <cppconn/statement.h> #include <cppconn/resultset.h> #include <cppconn/prepared_statement.h> #include <cppconn/exception.h&g…