C/C++数据类型转换详解

news2025/5/23 23:06:27

目录

C语言数据类型转换

1、自动类型转换

(1)算术表达式的自动类型转换

(2)赋值运算中的自动类型转换

2、强制类型转换

C++数据类型转换

1、static_cast<>

2、const_cast<>

3、dynamic_cast<>

4、reinterpret_cast<>


C语言数据类型转换

1、自动类型转换

(1)算术表达式的自动类型转换

在实际运算中,整型(int,short,long,char)和浮点型(float,double)是可以混合运算的,例如下面这段代码就是合法的。

10+'a'+1.5-8765.1234*'b'

只是在算术表达式进行运算的过程中,不同类型的数据要自动转换成同一类型,然后进行运算。

算术表达式中自动类型转换的具体规则:

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。

横向向左的箭头表示必定的转换,如char,short型数据在运算时必定先自动类型转换为int类型(char,short自动类型转换为int也称为整型提升),float型数据在运算时一律先自动类型转换为double类型以提高运算精度。

纵向的箭头表示当运算对象为不同类型时转换的方向,类型转换方向由低到高,如某类型数据与double型数据进行运算时,该类型数据自动类型转换为double类型,然后在两个double型数据间进行运算,结果为double型。如果表达式中,类型转换的最高级别是long,那么其它类型的数据就会自动类型转换为long类型。以此类推…

注:

  • 不要错误地将该图理解为int型先转换成unsigned int型,再转换成long型,再转成double型
  • 在算术表达式中进行自动类型转换的时候,原数据类型并无发生变化,实际上是得到一个自动类型转换后的中间变量,表达式运算的过程是对转换后的中间变量进行运算,运算结果为转换后的数据类型

如图所示b+c在进行运算的时候肯定会发生自动类型转换,但是我们最终算b的大小发现它依然是2字节,这就说明原数据类型并 没有发生变化,在运算过程中发生的自动类型转换实际上得到的是一个自动类型转换后的中间变量以参加运算,我们再输出b+c 的大小发现它是4字节,这就说明运算后的结果类型就是转换后的数据类型。

接下来我们来看下面的例子。

例1:

int i;
float f;
double d,result;
long e;
result = 10 + 'a' + i * f - d / e;

10+'a’在进行运算的时候,会先将’a’自动类型转换为int类型,即97,运算结果为107。i * f在进行运算的时候,会将i与f都转换成double类型,运算结果为double类型。整数107与i*f的积相加,先将整数107转换成double类型,最终运算结果为double类型。d/e在进行运算的时候,会将变量e转换为double类型,所以d/e的结果为double类型。所以最终的结果就是double类型。

例2:

int main()
{
	char c = 1;
	
	printf("%u\n", sizeof(c));
	printf("%u\n", sizeof(+c));
	printf("%u\n", sizeof(-c));
	printf("%u\n", sizeof(!c));
	
	return 0;
}

在该例中,c只要参与表达式运算,就会发生自动类型转换,表达式 +c ,就会使得变量c自动类型转换为int(整型提升),所以 sizeof(+c) 是4个字节,表达式 -c 也会自动类型转换为int(整型提升),所以 sizeof(-c) 是4个字节,但是 sizeof( c) 和sizeof(!c),就是1个字节。

例3:

一道经典面试题,下列代码的输出结果是什么:

#include <stdio.h>
int i;
int main()
{
    i--;
    if (i > sizeof(i))
    {
        printf(">\n");
    }
    else
    {
        printf("<\n");
    }
    return 0; 
}

A.>

B.<

C.不输出

D.程序有问题

解析:

C语言中,0为假,非0即为真。

全局变量,没有给初始值时,编译器会默认将其初始化为0。

i的初始值为0,i–结果-1,i为整形,sizeof(i)求i类型大小是4,按照此分析来看,结果应该选择B,但是sizeof的返回值类型实际为无符号整形,因此编译器会将左侧i自动转换为无符号整形的数据。

-1的原码:10000000000000000000000000000001

-1的反码:111111111111111111111111111111111110

-1的补码:111111111111111111111111111111111111

将-1转化为无符号整型,实际上就是将它的符号位视为有效位,故此时它是一个正数,正数的补码,反码,原码相同,故此时-1的原码就是11111111111111111111111111111111

所以-1对应的无符号整形是一个非常大的数字,超过4或者8,故实际应该选择A

有符号数与无符号数其实是一种看待内存中数据的角度,并不是绝对的,就比如我们把-1看作无符号数,那么他的符号位就会被视作为有效位

整型提升的原理:

无符号整形的整形提升,高位补0

有符号整型的整形提升是按照变量的符号位来提升的,即高位补符号位。

负数的整型提升如下:

char c1 = -1;
//-1是int类型的整数,存储在char类型的变量中会进行高位截断
//变量c1的二进制位(补码)中只有8个比特位:1111111
//因为char为有符号的char,所以整形提升的时候,高位补充符号位,即为1
//提升之后的结果是:11111111111111111111111111111111

正数的整型提升如下:

char c2 = 1;
//变量c2的二进制位(补码)中只有8个比特位:00000001
//因为 char 为有符号的 char,所以整形提升的时候,高位补充符号位,即为0
//提升之后的结果是:00000000000000000000000000000001

(2)赋值运算中的自动类型转换

C语言规定:如果赋值运算符两侧的类型不一致,但都是数值型或字符型时,在赋值时会进行自动类型转换。

赋值运算中自动类型转换的规则:

  • 将浮点型数据赋值给整型变量时,舍弃浮点数的小数部分。如这段代码:int i = 8.24;i的值为8,在内存中以整数形式存储
  • 将整型赋值给浮点型变量时,数值不变,但以浮点数的形式存储到变量中,如float f = 35;,先将35转换成35.00000,再存储到f中。double d = 35;,则将35转换成35.00000000000000,然后以双精度浮点数形式存储到d中。
  • 将一个double型数据赋值给float类型变量时,截取前面7位有效数字,存放到float变量的存储单元(32位)中,但是应注意数值范围不能溢出
  • 将一个float类型数据赋值给double类型变量时,数值不变,有效位数扩展到16位,在内存中以64位存储。
  • 将字符型数据赋值给其它整型变量时,由于字符只有1个字节,而其它整型变量至少为2个字节,因此将字符数据放到整型变量的低八位中,将其他整形变量赋值给char型变量时,只将其低8位原封不动地送到char型变量(高位截断)。

例1:

int i = 289;
char c = 'a';
c = i;
//以%d形式输出,c的值为33,以%c形式输出,得到字符’!'(其ASCII码值为33)

例2:

int main()
{
  unsigned char a = 200;
  unsigned char b = 100;
  unsigned char c = 0;
  c = a + b;
  printf("%d\n", c);
  return 0;
}

c=a+b这句代码在运算的过程中,a和b会发生整型提升,但是在存储的时候,变量c还一个char类型的变量,所以存不下去整型提升后的结果,所以会发生高位截断。

例3:

int main()
{
	char a = 0xb6;
	short b = 0xb600;
	int c = 0xb6000000;
	
	if(a==0xb6)
		printf("a");
	if(b==0xb600)
		printf("b");
	if(c==0xb6000000)
		printf("c");
	
	return 0;
}

0xb6,0xb600是int类型的整数分别存储在char类型和short类型的变量,所以发生了截断,故变量a和变量b的值实际上并不分别等于0xb6,0xb600,所以最终输出c。

2、强制类型转换

自动类型转换常被称为隐式类型转换,强制类型转换常被称为显示类型转换,强制类型转换的一般形式为(类型标识符)(操作数)或(类型标识符)(表达式)。其功能就是把操作数或表达式结果的数据类型暂时地强制转换为圆括号()中的类型。

类型标识符两边的圆括号就是C语言中的强制类型转换符。

在进行强制类型转换时,原来变量的类型未发生变化,我们所得到的是一个所转类型的中间变量。 如:

int main()
{
	float x;
	int i;
	x = 3.6;
	i = (int)x;
	printf("x = %f,i = %d", x, i);
	
	return 0;
}

在进行强制类型转换后得到的是一个int型的中间变量,它的值等于x的整数部分,而x的类型不变,仍为float型,x的值也不变。

输出结果如下:

在了解了强制类型转换后,我们简单地看一下强制类型转换的例子:

int main()
{
	int a = 20000000, b = 30000000, x;
	x = a * b;
	printf("%d*%d = %d", a, b, x);
	
	return 0;
}

输出结果如下:

这样的输出结果显然是错误的,因为正确的结果显然超过了int的范围,发生了溢出错误,截去了高位部分,那如果我们把程序改为下面这样呢?

int main()
{
	long long x;
	int a = 20000000, b = 30000000;
	x = a * b;

	printf("%d*%d = %d", a, b, x);
	
	return 0;
}

运行结果依然为:

因为a和b就是int型,a*b也是int型,这时已经产生了溢出错误,所以只是把高位截断后的值赋值给了long long型的变量,然后将左边高位补零,数值大小不变。

要得到正确的结果,可以用强制类型转换把程序改为

int main()
{
	long long x;
	int a = 20000000, b = 30000000;
	x = (long long)a * b;

	printf("%d*%d = %lld", a, b, x);
	
	return 0;
}

这样就能得到最终的正确结果:

C++数据类型转换

C++兼容C语言的类型转换风格,同时C++提供了自己的类型转换操作符,一共有四种,如下代码所示:

static_cast<new_type>(expression);
const_cast<new_type>(expression);
dynamic_cast<new_type>(expression);
reinterpret_cast<new_type>(expression);

下面就对这四种类型转换操作符进行总结:

1、static_cast<>

static_cast<>是最常用的类型转换操作符,它主要执行非多态的转换,用于代替C语言中通常的转换操作,但同时它还可以用于类层次结构中基类和子类之间指针或引用的转换,在进行上行转换(把子类指针或引用转换成基类类型)是安全的,但是在进行下行转换(把基类指针或引用转换成子类类型)时,由于没有动态类型检查,是不安全的。

根据代码进行分析,首先是将double类型的变量a转化为int类型,但是根据输出结果发现,变量a依然是double类型,所以和C语言数据类型转换一样,只是创建了一个转换类型后的临时变量,然后将临时变量的值赋值给指定变量,被转换的原变量本身类型不变。然后下面也进行了上行转换和下行转换,只是下行转换是没有动态类型检查的,是不安全的。

2、const_cast<>

const_cast<>在进行类型转换时用来删除类型的const或volatile属性,除了const或volatile修饰之外,原来的数据值和数据类型都是不变的,const_cast<>中的数据类型必须是指针或者引用。

首先将const指针p转换为非const指针然后赋值给指针变量p2,转换成功,因为如果没有将const指针p转换为非const指针,那么就是将const指针赋值给非const指针p2,这是权限的放大,编译器会报错,后面通过指针修改p所指变量的值,编译器报错,说明指针p依然是const的,只是创建了一个转换后的非const的临时变量,然后将这个非const临时变量赋值给p2,被转换的原变量本身的const和volatile属性是不变的。

3、dynamic_cast<>

该操作符用于运行时检查类型转换是否安全,但只在多态类型时合法,即该类型至少具有一个虚函数。它与static_cast具有相同的语法,但dynamic_cast主要用于类层次间的上行和下行转换,以及类之间的交叉转换。在类层次间进行上行转换时,它和static_cast效果是一样的,在进行下行转换时,它具有类型检查的功能,比static_cast更安全,dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0。

4、reinterpret_cast<>

该操作符将一个类型(如int类型)的数据a转换为另一个类型(如double类型)的数据b时仅仅是将a的比特位复制给b,不作数据转换,也不进行类型检查。而且reinterpret_cast要转换的new_type类型必须是指针类型、引用或算术类型,例如下面的代码:

char c = 'a';
int d = reinterpret_cast<int&>(c);

这个转换只是单纯的将字符c中的比特位复制给了d,并没有进行必要的分析和类型等的检查。这种转换方式一般较少使用。

本篇文章部分内容参考书籍《C语言程序设计》(刘天印 冯运仿 主编)和博客链接 https://book.itheima.net/course/223/1275663370879508481/1275663662815649798

 

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

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

相关文章

【Allegro软件PCB设计120问解析】第78问 如何在PCB中手动添加差分对及自动添加差分对属性呢?

答:设计PCB过程中,若设计中有差分对信号,则需要将是差分的2个信号设置为差分对,设置差分对有2种方式:手动添加及自动添加。 1、 手动添加差分对: 第一步,点击Setup-Constraints-Constraint Manager调出CM规则管理器,然后到Physical规则管理器下点击Net-All Layers,然…

React脚手架+组件化开发+组件生命周期+组件通信

react脚手架&#xff08;create-react-app&#xff09; 1.作用&#xff1a; 帮助我们生成一个通用的目录结构&#xff0c;并且已经将我们所需的工程环境配置好 2.依赖环境 脚手架都是使用node编写的&#xff0c;并且都是基于webpack的&#xff1b; 3.安装node 4.安装脚手架 n…

在哔站黑马程序员学习Spring—Spring Framework—(一)核心容器(core container)学习笔记

一、spring介绍 spring是一个大家族全家桶&#xff0c;spring技术是JavaEE开发必备技能。spring技术解决的问题&#xff1a;一是简化开发&#xff0c;降低企业级开发的复杂性。二是框架整合&#xff0c;高效整合其他技术&#xff0c;提高企业级应用开发与运行效率。 spring技术…

git revert和git reset的差异点和区别

git revert 定义 撤销某次提交,此次撤销操作和之前的commit记录都会保留。 git revert会根据commitid找到此次提交的变更内容&#xff0c;并撤销这些变更并作为一次新commit提交。 ps:此次commit和正常commit相同&#xff0c;也可以被revert revert和reset有本质的差别&#…

使用C#开发ChatGPT聊天程序

使用C#开发ChatGPT聊天程序 总体效果如下&#xff1a; 源码下载 关键点1&#xff1a;无边框窗口拖动 Window中设置AllowsTransparency"True"、 Background"Transparent"以及WindowStyle"None"这样设置的话默认窗口的标题栏为不可见状态&…

cocos tween

缓动接口Tween 属性和接口说明接口说明接口功能说明tag为当前缓动添加一个数值类型&#xff08;number&#xff09;的标签to添加一个对属性进行 绝对值 计算的间隔动作by添加一个对属性进行 相对值 计算的间隔动作set添加一个 直接设置目标属性 的瞬时动作delay添加一个 延迟时…

Spring Cloud服务发现组件Eureka

简介 Netflix Eureka是微服务系统中最常用的服务发现组件之一&#xff0c;非常简单易用。当客户端注册到Eureka后&#xff0c;客户端可以知道彼此的hostname和端口等&#xff0c;这样就可以建立连接&#xff0c;不需要配置。 Eureka 服务端 添加Maven依赖&#xff1a; <…

菜谱分享APP/基于android菜谱分享系统

摘 要随着现代生活水平的不断提升&#xff0c;人们越来越关注健康,关注美食,大部分人都希望吃得美味的同时也要吃得健康,所以,有的人喜欢在家自己动手制作美食,但是却缺少这方面的资讯来源。菜谱分享APP是一个使用Hbuildex作为手机客户端和后台服务系统的开发环境, MySQL作为后…

Node.js+Vue.js全栈开发王者荣耀手机端官网和管理后台(一)

文章目录【全栈之巅】Node.jsVue.js全栈开发王者荣耀手机端官网和管理后台(一)工具安装和环境搭建初始化项目基于ElementUI的后台管理基础界面搭建创建分类&#xff08;客户端&#xff09;创建分类&#xff08;服务端&#xff09;分类列表分类编辑分类删除子分类【全栈之巅】No…

Bubbles原理解析

官方文档 https://developer.android.com/develop/ui/views/notifications/bubbles#the_bubble_api 气泡使用户可以轻松查看和参与对话。 气泡内置于通知系统中。 它们漂浮在其他应用程序内容之上&#xff0c;无论用户走到哪里都会跟随他们。 气泡可以展开以显示应用程序功能和…

如何搭建春节值班智能通知方案

春节期间&#xff0c;为了保证业务的正常运转&#xff0c;或者及时响应用户/客户需求&#xff0c;许多公司会安排一些人员轮流进行值班&#xff0c;解决一些突发情况或者问题。在过去&#xff0c;一般是排好值班表后&#xff0c;全靠人工自觉上线进行值班&#xff0c;容易遗忘或…

干货,游戏DDoS攻击趋势及原因分析,附防御案例

我曾看到充满激情的创业团队、一个个玩法很有特色的产品&#xff0c;被这种互联网攻击问题扼杀在摇篮里&#xff1b; 也看到过一个运营很好的产品&#xff0c;因为遭受DDoS攻击&#xff0c;而一蹶不振。这也是为什么想把自己6年做游戏行业DDoS的经验&#xff0c;与大家一起分享…

网络的基础认知

日升时奋斗&#xff0c;日落时自省 目录 1、网络发展史 1.1、独立模式 1.2、局域网 1.3、广域网 2、网络通信基础 2.1、IP地址 2.2、端口号 2.3、协议 2.4、网络协议分层 2.4.1、协议类别 2.4.2、TCP和UDP区别 2.5、分层理解事例 2.5.1 封装 2.5.2分用 2.5.3、总…

114. 二叉树展开为链表

114. 二叉树展开为链表 难度中等 给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。展开后的单链表应该与二叉树 先…

04 链式队列的实现

带头节点的链式队列&#xff1a; 初始化&#xff1a;rear和front指针都指向头节点入队&#xff1a;向rear指向的节点后插入新节点&#xff0c;并让rear指针移动指向新的队尾节点出队&#xff1a;front指针始终指向头节点&#xff0c;即删除头节点后一个节点&#xff1b;最后一个…

Arduino,Nodemcu,ESP8266网络连接(局域网),网页控制内部LED状态变化

整体思路&#xff08;无线终端模式&#xff09; 需要注意&#xff1a;esp8266是 802.11n⽀支持**(2.4GHz )&#xff0c;也就是平常说的wifi4&#xff0c;WiFi5G是连不上的** 配置指定的LED接口&#xff0c;配置网络的WiFi&#xff0c;记录ESP8266模块做为服务器的IP&#xff0…

2022某站百大up主公布名单,python如何快速获取up名单

前言 2022年已经过去&#xff0c;各大厂商都在做年度总结。某站在1月13日中午19点30分公布了2022百大UP主名单&#xff0c;那么今年的某站年度UP主都是谁呢&#xff1f;接下来就让我们一起了解一下吧。不过&#xff0c;我们去用python获取名单&#xff0c;是非常有成就的。 环…

Git(1)基础命令

Git基础知识总结 Author&#xff1a;onceday date&#xff1a;2022年8月8日 满满长路有人对你微笑过嘛… Modified date&#xff1a;2022年11月22日&#xff0c;完成第一章部分内容。 windows安装可参考文章&#xff1a;git简易配置_onceday_CSDN博客 參考文档&#xff1a…

腾讯云短信服务——获取验证码

引言 之前介绍过阿里云短信服务&#xff0c;传送门&#xff1a;阿里云短信服务——短信发送验证码&#xff0c;但是由于阿里云现在短信调用门槛较高&#xff0c;申请有很多限制&#xff08;我申请好几次都没有通过&#xff09;&#xff0c;所以只能使用测试账号给固定的手机号…

2023速卖通开店入驻流程及费用,新店运营思路

开店前准备 从卖家开始注册账户起&#xff0c;需速卖通平台的销售政策和全球速卖通平台规则。平台要求卖家在速卖通经营需遵循公平、诚实的行事原则&#xff0c;以确保安全的购买及销售体验。 1.注册账号 注册账户前&#xff0c;以下所有资料都是必须提供的资料。若资料不完…