1. 接下来讲一下表达式求值,这个东西也非常非常关键。我们之前讲了那么那么多操作符,那么这些操作符到底是用来干什么的呢?它就是放到表达式里面,从而来影响我们的表达式求值。你写出的任何一个表达式,它里面都含有操作符,这些操作符从而会影响你表达式的计算。
2. 表达式求值的顺序一部分是由操作符的优先级和结合性,同时表达式在计算的时候可能要进行类型转换。
整形提升操作对象是内存中的二进制补码
一,隐式类型转换
所谓隐式类型转换,也就是偷偷的进行类型的转化,可能你压根就没有意识到它的存在,但是它时刻都在发生。其中有一种隐式类型转换叫做“整型提升”。
1. C语言的整型算术运算总是至少以缺省(默认)整型类型的精度来进行的,就是说至少也得是一个整型类型,以这样的精度来计算。那么为了获得这个精度,表达式里面的字符和短整型操作数(什么是操作数自己应该懂得噢)在使用之前会被转换为普通整型,这种转换就被称为整型提升。
2. 比如说两个字符类型的变量a. b,我的一个表达式里面有c=a+b,而我事实上表达式在计算的时候,我并不是两个字符直接相加,而是先要把a和b都转化为普通整型,这种转化就被成为整型提升。
3. 整型提升针对的是类型小于整型的(我们的类型中最小的是char(一个字节),在下来是short(2个字节),然后是int,后面还有long等等),因此在大小上面,char与short是要小于int 的,因此如果是char与short类型的变量在表达式中计算的时候,首先会把char或者short提升为int类型,然后再去参与运算,这就是所谓的“整型提升”。
4. 那么为什么整型提升要存在,它的意义又在于何呢?因为一个表达式的整型运算,需要在CPU(中央处理器,是用来完成计算任务的)里面的相应运算器件内执行。CPU里面的整型运算器(ALU)的操作数(它所占)的字节长度一般就是int类型的字节长度(四个字节),同时也是CPU通用寄存器的长度。因此,即使是两个char类型的相加,在CPU执行时实际上也要转化为CPU内整型运算器的操作数的标准长度。这一切都是为了适应我们的CPU。
通用CPU是难以直接实现两个8比特直接相加运算,虽然机器指令中可能有这种字节相加指令。所以表达式中各种长度可能小于int长度的整型值(这个所谓的长度=该变量在内存中所占的字节长度,int类型是占4个字节),都必须先转换为int或者unsigned int,然后才能送到CPU里面去执行运算。
5.那么接下来问题就又来了,这个整型提升到底是怎么去提升的呢?整型提升是按照变量的数据类型的符号位来提升的。所以说从字面上理解也可以知道补的是符号位,因为提升它要变长补长嘛。
简而言之就是这样:
1. 先看你这个数据的数据类型是有没有符号的。
2. 如果是没有符号的类型,那么就高位补0,也就是说把它(二进制补码)补到32位这个长度。
3. 如果是有符号的类型,注意: 此时2进制补码的最高位就是符号位,高位补充符号位。把它(二进制补码)补到32位这个长度。
在以下面这个为例:
1. 我举个简单的例子: char a = 3,当我这么去创建一个变量的时候,我在内存里面开辟的空间因为char类型,因此我在内存里面只开辟了一个字节大小(8比特位)的空间,但我的3是一个整数(按道理来说占四个字节),我们知道它可以化成32位的二进制序列。但是现在我要把这个32位的二进制序列给它放到只有八个比特位的这么一个内存空间里面去,这时候必然会发生截断。事实上,这个a(八个比特位)里面放的只有00000011,其他的都已经被截断截掉了。
2. char b = 127,到底也是一样的,当我这么去创建一个变量的时候,我在内存里面只开辟了一个字节大小的空间留给变量b,但我127这个整数可以化成32位的二进制序列,当我塞给b的时候,由于内存空间只有八个比特位,这时候再次发生截断,这个b(八个比特位)里面放的只有01111111,其他的都已经被截断了。
3. 然后char c = a + b,现在当a和b进行相加的时候(发生了需要CPU参与的运算)这里面只有一个字节的数据,为了适应CPU,我们必须得进行整型提升,上面我们已经讲过了,是根据数据类型的符号位来进行提升,也就是说补的是符号位。我先来提升a,a的类型是char,在当前编译器下,char是有符号的(等价于signed char),那我在提升的时候,高位补的就是符号位(就看当前最高位的数字),也就是补0,00000000000000000000000000000011。然后提升b的时候,b的类型是char,有符号的,高位补符号位,也是补0,00000000000000000000000001111111。这个时候我就提升完了
4. 然后加法就是两个二进制序列相加(其实也就是每一位相加),这时候需要注意一点:二进制里面遇到2的时候需要余0进1,比如说遇到3的时候需要余1进1,因此我加完的结果是00000000000000000000000010000010,这个32位的序列是一个整型。
5. 然后我又要放到c里面去,这个又c只占一个字节,这时候再次发生截断,c里面放的最终是10000010这玩意儿。
6. 然后接下来比如说我要printf("%d",c),%d是打印十进制的整数,而我们的c是char类型的变量,因此在打印的时候也需要整型提升,然后一看c,类型是char,有符号的,然后就是按符号位(当前的最高位)来提升,于是就变成了11111111111111111111111110000010。
7. 那是我提升后的结果,也是内存里面的值,既然在内存里面,那就一定是补码,那如果打印在屏幕上,让我们肉眼能够看到,那应该得先转化为原码,那么原码就为:100000000000000000000001111110,为-126。
再次强调,只有负数的原码补码要进行转化,而正数的原码反码不用,一样的
上面这是一个例子,然后接下来又是一个例子
既然底层原理知道了,那现在也可以推算出来有符号的char的取值范围为-128~127,无符号的char的取值范围为0~255
1. 首先到现在为止,我相信都有这么一个感觉:比如说我定义一个char类型的变量,那么我在内存当中开辟了一个字节的空间。比如说我定义一个int类型的变量,那么我在内存当中开辟了四个字节的空间。
假设我们讨论的是有符号的char(八个比特位,每个比特位可以放0和1,因此总共有2的8次方种二进制序列排列方式)。既然是有符号的,那么最高位的这一位就是符号位。我们都知道,符号位为0,表示是正数;符号位为1,表示是负数。这时候有又必须得强调一点的是:内存当中存的是补码!内存当中存的是补码!打印在屏幕上的数字要先把补码转化成原码!打印在屏幕上的数字要先把补码转化成原码!(同时要记牢:正数的原码反码补码都一样,内存当中如果是全1,无论是32位还是8位序列,打印在屏幕上都是-1)。然后就正数而言,表示值最大的形式是01111111(127),对于负数而言,请看我的草稿纸。(特别的规定:补码 1后面全是0,解析过来为-128)
接下来讨论无符号的char,在这种情况下,最高位已经不是符号位了,序列中的每一个数都要老老实实乘以它的权重,都是有效位。因此很容易得到对于无符号的char,它的范围就是:0~255
上面这是一个例子,然后接下来又是一个例子
“表达式”这个涵盖的范围很广,不仅是加减乘除,大于小于等于之间比较这些都称为表达式。
而如果代码语句中出现0xb6,0xb600等等,这些东西不需要整型提升,这个本来就是一个整数(只是以16进制表示而已)
上面这是一个例子,然后接下来又是一个例子
sizeof(+a)与sizeof(-a)都是表达式,一旦表达式参与运算,那些short与char类型的都必须整型提升以适应CPU。
1. 我们上面讲了整型提升,那我们是指表达式里面的char, short类型需要提升,那如果表达式里面没有char,short,而是那些int,long,float,double,它的所有这些操作数都是大于等于整型的,那又怎么办呢?这个时候就需要讲第二个点:算术转换。
2. 如果表达式某个操作符的各个操作数属于不同类型,除非其中一个操作数转换为另一个操作数的类型,否则操作无法进行。到底什么意思呢?比如说一个int类型的与另外一个float类型的两个操作数相加,因为它们各自的类型都是大于等于整型的,并且操作数之间类型不一样,这时候就不能直接进行操作,编译器会进行类型转换,也就是算术转换。会把int类型转换为float类型(这里有各种类型的转换顺序,有了这个优先级之后,就可以明白到底是谁转化为谁)
2 .算术转换讨论的类型都是大于等于整型类型的。当然算术转换也是隐式偷偷发生的。
3. 但是算术转换会有一些潜在的问题,比如说会有精度的丢失。
1. 上面我们讲了表达式里面不同类型的值在进行计算的时候,会进行哪些类型的转化。接下来我们在复杂表达式的求值的时候,必须得讲一下操作符的属性:操作服的优先级,操作符的结合性,是否控制求值顺序。
2. 对于优先级而言,一定是在相邻的操作符才讨论优先级。如果在表达式当中,两个操作符不相邻,无优先级之说好吧。
3. 首先确定优先级,相邻操作符按照优先级高低计算,如果(相邻)操作符的优先级相同(也就是两个操作符一毛一样了),这时候结合性才起作用。N/A表示没有结合性,L-R表示从左向右结合,R-L表示从右向左结合
4. 接下来就是是否控制求值顺序,一般的操作符都是否,但是也有个别操作符能够控制求值的顺序,比如说逻辑与,一旦左边发现有假,右边就不会执行下去;逻辑或,一旦发现左边有真,右边也不会执行下去。包括还有条件操作符,一样道理。
1. 虽然我们已经学了操作符的优先级,结合性以及是否控制求值顺序,但有可能还是不能确定一个表达式的唯一计算逻辑
2. 操作符有优先级,但是操作数(特别是那些不是确定的,诸如互相有联系的表达式)到底哪个先到位与起效,这个不一定有顺序之分,因此就会产生一些问题表达式