C语言航路外传之隐式转换与优先级的那点事(你程序总是出bug的一个重要原因)

news2025/1/13 2:33:11

目录

一、表达式求值

二、隐式类型转换

1.基本概念

2.整型提升的意义

3.详解截断与整型提升的过程

4.char类型范围有关的一些事情

5.有关整形提升的一些案例

三、算术转换

四、操作符的属性

1.优先级表格

2.运算规则

3.一些问题表达式

(1)a*b+c*d+e*f

(1)c+ --c

(3)i=i-- - --i*(i=-3)*i++ + ++i

(4)调用函数时

(5)总结

总结


一、表达式求值

在我们前面介绍了那么多的操作符,我们肯定肯定是需要使用他们的,在使用他们的时候,就会出现各种各样很奇怪的状况。这是因为我们还没有了解一些优先级相关的知识和一些隐式类型转换的问题。所以,我们这部分就来仔细描述一下有关类型转换的那些事。

表达式求值的顺序一部分是由操作符的优先级和结合性来决定的

同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型

二、隐式类型转换

1.基本概念

所谓隐式类型转化,就是偷偷的发生转换,你没有察觉到的一些转换。这种转换,如果不了解,往往会出现一些难以发现的错误。

c的整型算术运算总是以缺省整型类型的精度来进行的(缺省的意思是默认)

为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升

我们直接举一个例子

#include<stdio.h>
int main()
{
	char a = 3;
	char b = 127;
	char c = a + b;
	printf("%d", c);
	return 0;
}

由于我们总是以缺省整型类型的精度来进行计算的,所以我们这个char类型的数据在运算的时候会先转化为int类型。然后在进行计算,这就是整型提升

2.整型提升的意义

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。

因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。

通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令 中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转 换为int或unsigned int,然后才能送入CPU去执行运算

3.详解截断与整型提升的过程

整形提升是按照变量的数据类型的符号位来提升

无符号的整型提升直接补0

我们还是看这段代码

#include<stdio.h>
int main()
{
	char a = 3;
	char b = 127;
	char c = a + b;
	printf("%d", c);
	return 0;
}

这段代码他一开始是将3赋给a,这里要注意,这个3是一个整数,而整数是四个字节,也就是32个比特位,我们将鼠标放在编译器中的这个3上

我们从这里也能看出来,3是一个整型的。所以我们得先将3的二进制序列写出来

他的原码、反码、补码均为:00000000 00000000 00000000 00000011

而我们这个3是要存放到a里面去的,a是一个char类型,他只有一个字节,所以会发生截断现象,从右往左数,拿走他需要的比特位,其余的统统砍掉不要了

所以3放到a里面的二进制序列就变为了00000011

 同理b也一样

127的二进制序列为00000000 00000000 00000000 01111111

127放到b里面去,自然要发生截断现象

所以此时a与b要进行相加

而此时由于整型提升补的是数据的符号位,我们这个char类型其实本质上应该是signed char,是有符号类型的,所以最高位就是符号位 ,所以补符号位,补成int类型的字节

 a提升后为00000000 00000000 00000000 00000011  补的是符号位,符号位为0

 b提升后为00000000 00000000 00000000 01111111  补的是符号位,符号位为0

 a与b提升后相加为00000000 00000000 00000000 10000010

 而我们相加后的结果又要放到c里面去。这里就又发生截断了

此时c为10000010

 此时,接下来就要进行打印了。%d是以十进制进行打印,c为char类型,因此又要发生整型提升了,c为11111111 11111111 11111111 10000010,按符号位进行提升

 要注意的是,我们的数据在内存中都是补码的形式,进行计算的,我们上述的操作都是补码,所以此时提升后的也是一个补码。既然是补码就要转换为原码了

原码为10000000 00000000 00000000 01111110

 转换成十进制数就是-126

所以最终打印出来的结果就是-126

4.char类型范围有关的一些事情

char------有符号类型的char取值范围是:-128~127

               无符号的char取值范围是:  0~255

那么这些范围是如何得到的呢?实际上是计算出来的,而不是规定出来的

因为一个char类型是一个字节,也就是八个比特位,因此他的二进制序列的可能性就下面图中所示的这些,而这些总共有256种可能性

 假设我们现在讨论的是有符号的数,在下面图中所示的二进制中,第一位代表的是符号位,0为正数,1为负数

我们把这些东西存到内存中就叫做补码。然后将他们分别计算出来就是这些数

 所以有符号类型的char范围就是-128~127

而无符号类型的就很简单了,因为他们没有负数。所以直接就是他们计算成的十进制数,如下图所示,所以他的范围就是0~255

5.有关整形提升的一些案例

我们看这段代码,并思考运行结果

#include<stdio.h>
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;
}

运行结果为

这是因为a和b他都是小于int类型的,所以都会发生整型提升。而他们的符号位都是1,所以补的都是1,肯定不一样。所以为c。(上面这些数都是16进制数。两个十六进制数代表一个字节)

还有这一段代码

#include<stdio.h>
int main()
{
	char c = 1;
	printf("%u\n", sizeof(c));
	printf("%u\n", sizeof(+c));
	printf("%u\n", sizeof(-c));
	return 0;
}

这个运行结果为

 这是因为c进行了运算,所以被提升了。因为+,-也是一个操作符。

三、算术转换

小于int会发生整型提升,那么大于int呢?其实会发生算术转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。从上到下层级依次降低

long double

double

float

unsigned long int

long int

unsigned int

int

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

比如

a=3;

b=3.14;

c=a+b;

那么a首先会转换成b的类型进行计算

但是进行算术转换要合理,否则会出现问题

float a=3.14

int b=a;隐式转换,会出现精度缺失,但是编译器只会报警告,不会报错

四、操作符的属性

复杂表达式的求值有三个影响的因素。

1. 操作符的优先级

2. 操作符的结合性

3. 是否控制求值顺序。

相邻操作符才考虑优先级,两个相邻的操作符优先执行哪一个?取决于他们的优先级。如果两者的优先级相同,才取决于他们的结合性

1.优先级表格

操作符    描述用法用例结果类型结合性是否控制求值顺序
()聚组(表达式)与表达式同N/A
()函数调用rexp(rexp,....,rexp)rexpL-R
 [ ]下标引用rexp[rexp]lexpL-R
.访问结构成员lexp.member_namelexpL-R

->

访问结构指针成员rexp->member_namelexpL-R
++后缀自增lexp++rexpL-R
--后缀自减lexp--rexpL-R

逻辑反!rexprexpR-L

~按位取反~rexprexpR-L
+单目,表示正值+rexprexpR-L

-单目,表示负值-rexprexpR-L
++前缀自增++lexprexpR-L
--前缀自减--lexprexpR-L
*间接访问*rexplexpR-L
&取地址&lexprexpR-L
sizeof取其长度,以字节表示

sizeof rexp

sizeof(类型)

rexpR-L
(类型)类型转换(类型)rexprexpR-L
*乘法rexp*rexprexpL-R
/除法rexp/rexprexpL-R
%整数取余rexp%rexprexpL-R
+加法rexp+rexprexpL-R
-减法rexp-rexprexpL-R
<<左移位rexp<<rexprexpL-R
>>右移位rexp>>rexprexpL-R
>大于rexp>rexprexpL-R
>=大于等于rexp>=rexprexpL-R
<小于rexp<rexprexpL-R
<=小于等于rexp<=rexprexpL-R
==等于rexp==rexprexpL-R
!=不等于rexp!=rexprexpL-R
&位与rexp&rexprexpL-R
^位异或rexp^rexprexpL-R
|位或rexp|rexprexpL-R
&&逻辑与rexp&&rexprexpL-R
||逻辑或rexp||rexprexpL-R
?:条件操作符rexp?rexp:rexprexpN/A
=赋值lexp=rexprexpR-L
+=以...加lexp+=rexprexpR-L
-=以...减lexp-=rexprexpR-L
*=以...乘lexp*=rexprexpR-L

/=

以...除lexp/=rexprexpR-L
%=以...取模lexp%=rexprexpR-L
<<=以...左移lexp<<rexprexpR-L

>>=以...右移lexp>>rexprexpR-L
&=以...与lexp&=rexprexpR-L
^=以...异或lexp^=rexprexpR-L
|=以...或lexp|=rexprexpR-L

逗号rexp,rexprexpL-R

2.运算规则

首先确定优先级,相邻操作符按照优先级高低计算

优先级相同的情况下,结合性才起作用

我们看这样一段代码

#include<stdio.h>
int main()
{
	int a = 1;
	int b = 2;
	int c = 4;

	int d = a * 4 + b / 3 + c;
	return 0;
}

这个表达式先算a*4,然后计算b/3,然后计算第一个加法,然后计算第二个加法。

3.一些问题表达式

(1)a*b+c*d+e*f

a*b+c*d+e*f

这个表达式种,我们按照从左到右给他分别记作 1 2 3 4 5

那么他的计算顺序可以是1 3 2 5 4

也可以是1 4 2 5 3

这就出现两种运算方式。虽然结果是一样的,但是出现了两种计算方式,这就是很危险的行为了。比如将a看作一个表达式,如果他出现了副作用,那么势必会影响结果。

(1)c+ --c

这个也存在问题,我们知道先算--,然后计算加法

但是我们计算的是2 +2 呢还是3+2

也就是说虽然运算顺序知道了,但出现了副作用的表达式。第一个c会不会改变是取决于编译器的。看他是什么时候拿出他的值的。因此这个也存在潜在的危险

(3)i=i-- - --i*(i=-3)*i++ + ++i

这个代码,将他放在不同的编译器上,甚至每个编译器的结果各不相同。这是极其危险的代码

(4)调用函数时

#include<stdio.h>
int fun()
{
	static int count = 1;
	return ++count;
}
int main()
{
	int anwer;
	anwer = fun() - fun() * fun();
	printf("%d", anwer);//输出多少?
	return 0;
}

 这段代码也同样使得编译器凌乱了,不知道该如何做。不同的编译器有不同的结果

(5)总结

我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那么这个表达式是存在问题的


总结

本节主要讲解了表达式求值,隐式转换,整型提升,算术转换,操作符的优先级,结合性,以及是否控制求值顺序的一些知识点

如果对你有帮助,不要忘记点赞+收藏哦!!!

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

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

相关文章

Linux 之 arm linux 与 windows 使用 tftp 进行文件传输的简单整理

Linux 之 arm linux 与 windows 使用 tftp 进行文件传输的简单整理 目录 Linux 之 arm linux 与 windows 使用 tftp 进行文件传输的简单整理 一、简单介绍 二、tftp 下载安装 三、arm linux 通过tftp 下载 windows 上的文件 四、tftp 命令相关说明 1、busybox tftp命令常…

Baklib|知识库应用场景:制作员工培训手册

持续的专业发展对于想要加入、保留和提升员工的组织来说是必不可少的。为了确保员工总是能从学习能力中受益&#xff0c;您需要考虑创建培训手册&#xff0c;使员工能够胜任并保持他们的工作能力。 在过去&#xff0c;您可能认为培训手册是一本厚重的册子&#xff0c;充满了密…

如何理解电商云仓出租?

为了说清这个概念&#xff0c;我们先说一下云的概念。别看云现在已经漫天飞舞&#xff0c;但真正能准确的把它说明白的并不多&#xff0c;多数人只是跟着一起用而已。云起源于云计算&#xff0c;是指通过云计算技术各种终端设备手机、电脑、电视、车载屏幕等实现互通互联&#…

【教学类-19-03】20221127《ABBABB式-规律排序-A4竖版2份》(中班)

展示效果&#xff1a; 单人使用样式&#xff1a; 单页打印样式 ​ 背景需求&#xff1a; 中班幼儿需要掌握ABBABB的排序规律 前文制作了ABCABC单元格色块&#xff0c;完全套用ABC排序word表格&#xff0c;调整python的代码&#xff0c;随机生成ABB排序样式&#xff0c;引导幼…

1_SpringMVC_概述,2_SpringMVC_项目搭建

M model 模型层 DAO封装 >>> Mybatis V view 视图层 html css js jsp C controller 控制层 Servlet封装 >>> springMVC SpringMVC是spring为展现层提供的基于MVC设计理念的优秀WEB框架,是目前最主流的MVC框架之一 …

疫情下如何保持高效项目交付

作者&#xff1a;饶炯 疫情以来&#xff0c;我们工作和生活的正常秩序不时受到影响&#xff0c;项目开发过程中不时出现项目成员被封闭或开发现场封锁的情况&#xff0c;很多项目不得不临时选择远程开发模式&#xff0c;项目沟通和协作中出现了各种新的问题和困难&#xff0c;严…

[附源码]计算机毕业设计springboot4S店汽车售后服务管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Python库的使用

文章目录库的使用标准库认识标准库import导入模块使用示例&#xff1a;日期差计算使用示例&#xff1a;字符串操作1&#xff09;翻转单词顺序2&#xff09;旋转字符串3&#xff09;统计是给定字符串前缀的字符串数目第三方库认识第三方库包管理器pip使用示例&#xff1a;生成二…

QFile(文件)

QFile QFile提供一个用于读/写的接口&#xff0c;是一个可以用来读/写二进制文件的Qt资源的I/O设备&#xff0c;QFile可以单独使用&#xff0c;一般配合QTextStream或QDataStream 输入文件路径时最好使用"/"作为分隔符 构造函数&#xff1a; 常用的函数&#xff1a;…

前端甘特图组件开发(二)

自定义时间轴功能 由于常见的甘特图时间轴只支持按照天/周/月/年或者固定的时间单位进行划分&#xff0c;但实际使用场景下有时候会需要按照特定且不规则的时间段对进度数据进行直观划分。因此本组件在原时间轴的基础上添加新的自定义时间轴&#xff0c;可通过数据配置自定义时…

Spring Bean的作用域

1.写在前面 前面的博客我们已经介绍完Spring的依赖的查找来源&#xff0c;依赖注入的来源等等相关知识&#xff0c;今天我们继续来介绍Spring的Bean的作用域。 2.Spring Bean作用域 作用域 3.“singleton” Bean作用域 配置 4.“prototype” Bean作用域 配置 注意事项 Sp…

[Redis]-持久化方式

[Redis]-持久化方式 森格 | 2022年11月 本文是在学习Redis中&#xff0c;对Redis持久化的个人总结。 一、 持久化与Redis 1.1 什么是持久化 持久化是一种将程序数据在瞬时状态和持久状态间的转换机制&#xff0c;也就是把数据保存到可永久保存的存储设备中去。 1.2 Redis的持…

【Hack The Box】linux练习-- Writer

HTB 学习笔记 【Hack The Box】linux练习-- Writer &#x1f525;系列专栏&#xff1a;Hack The Box &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年11月27日&#x1f334; &#x1f3…

《web课程设计》使用HTML+CSS制作大学生校园二手交易网站

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

阿里P8现身说法,解密“架构”原理与实战笔记:从分布式到微服务

分布式架构与微服务平台是当今IT界的关键技术&#xff0c;也是资深软件工程师和系统架构师必须掌握的核心技术。 微服务、云原生、Kubernetes、Service Mesh是分布式领域的热点技术&#xff0c;它们并不是凭空出现的&#xff0c;一定继承了某些“前辈”的优点。我们不仅要了解…

十万部冷知识:足球踢进了观众席,观众能把球拿走吗?

在看足球比赛的时候&#xff0c;我们经常会看到球员一脚射门踢偏了&#xff0c;球被打飞的情况&#xff0c;这时候球可就奔着观众席去了。就是因为考虑到有这种情况的发生&#xff0c;在比赛之前&#xff0c;比赛组织者&#xff0c;都会提前准备很多的备用球&#xff0c;当球被…

R11.8-11.8-11.8-11.8-BABSL

R11.8-11.8-11.8-11.8-BABSL R11.8-11.8-11.8-11.8-BABSL哈威柱塞泵宁波秉圣&#xff0c;主要特点是高自吸转速&#xff0c;工作效率高&#xff0c;结构紧凑&#xff0c;工作压力高等。广泛用于压力机器具&#xff0c;测试和实验室设施&#xff0c;润滑装置等设备&#xff0c;在…

Home Assistant添加ESPHome设备(IO控制继电器)

文章目录1.在加载项商店中安装ESPHome2.添加设备2.1 编译并下载.bin到电脑2.2 下载固件到ESP82662.3 在Home Assistant添加并配置设备2.4 在设备与服务中配置3.再添加一个设备1.在加载项商店中安装ESPHome 点击配置-加载项 点击加载项商店 搜索ESPHome 2.添加设备 这里…

为什么我在公司里访问不了家里的电脑?

本文为掘金社区首发签约文章&#xff0c;14天内禁止转载&#xff0c;14天后未获授权禁止转载&#xff0c;侵权必究&#xff01; 上篇文章「为什么我们家里的IP都是192.168开头的&#xff1f;」提到&#xff0c;因为IPv4地址有限&#xff0c;最大42亿个。为了更好的利用这有限的…

前端学习一、准备工作

一、电脑 首先想学习前端肯定是需要一台电脑&#xff0c;配置方面目前市面上3千左右的电脑就差不多了&#xff0c;如果有能力的话肯定是越高越好&#xff0c;如何挑选笔记本可自行搜索&#xff0c;我在这方面不专业就不讲了。 二、安装软件 谷歌浏览器 如果无法访问谷歌浏览…