1、结构体
C语言中的结构体可以类比Java这种面向对象程序语言中的类,可以将其形象地理解为一种模具。
2、操作符优先级
操作符优先级决定的是两个相邻操作符的先后顺序,而非所有操作符。
例如:
a*b+b*c+c*d+d*f
计算机的执行逻辑是:
根据优先级的运算次序为:a*b+b
、(a*b)+b*c
、(a*b)+(b*c)+c
、((a*b)+(b*c))+c*d
、((a*b)+(b*c))+(c*d)+d
、((a*b)+(b*c)+(c*d))+d*f
、((a*b)+(b*c)+(c*d))+(d*f)
、((a*b)+(b*c)+(c*d)+(d*f))
。
括号内的表达式代表已经发生运算并得出值的表达式。
3、表达式求值
3.1、整型提升
C语言中整型家族参与算术运算时总是至少以缺省(默认)int
类型的精度来进行的。
为了获得这个精度,表达式中的字符、短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
根据数据的存储规则,可将一部分数据类型区分为整型家族:char
型、int
型,因为char
数据类型实际上存储的是ASCII码值,故属于整型家族。整型算数运算就是指整型家族的内部运算。
所有参与操作符运算的数据的格式都是它的存储格式,即整型家族参与运算的是补码(有符号数)或二进制数(无符号数),浮点型家族参与运算的是二进制数(IEEE754标准)。故而参与有符号整型算数运算的是补码,结果也是补码,发生整型提升后的是补码,发生截断后的也是补码,无论符号位是什么。
3.1.1、整型提升的意义
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一
般就是int
的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int
长度的整型值,都必须先转换为int
或unsigned int
,然后才能送入CPU去执行运算。
3.1.2、如何进行整型提升
1、有符号整数提升是按照变量的数据类型的符号位来提升的,符号位为1则高位全补1,为0则高位全补0。
2、无符号整数提升,则高位补0。
3.2、截断
强制类型转换和隐式类型转换的实质就是截断。
整数类型的截断就是去高位,留低位;浮点数类型的截断是仅对尾数码进行去低位,留高位。
详见:浮点数型存储的截断
所有的整数字面量都是默认为int
类型存在。
char a = 5;
整数字面量5的二进制形式是:00000000 00000000 00000000 00000101
。
当进行赋值运算时,由于char数据类型定义的变量的内存空间只有1字节,即:00000000
。
故而会发生截断,使得a中存入的数据为:00000101
。
再举一个例子:
char a = 5;
char b = 125;
char c = a + b;
printf("%d\n",c);
初始化b
时,
整数字面量125
的二进制形式是:00000000 00000000 00000000 01111101
。
进行赋值运算时,发生截断,使得b
中的数据为:01111101
。
初始化c
时,
发生了表达式求值,a
与b
都属于整型家族,且char
类型默认为有符号型,数据的最高位为符号位,故而发生的整型提升为:
a
整型提升后为:00000000 00000000 00000000 00000101
。
b
整型提升后为:00000000 00000000 00000000 01111101
。
进行加法运算后的结果为:00000000 00000000 00000000 10000010
。
进行赋值运算时发生截断,结果为:10000010
。
所有参与操作符运算的数据的格式都是它的存储格式,即整型家族参与运算的是补码(有符号数)或二进制数(无符号数),浮点型家族参与运算的是二进制数(IEEE754标准)。故而参与有符号整型算数运算的是补码,结果也是补码,发生整型提升后的是补码,发生截断后的也是补码,无论符号位是什么。
以占位符%d
输出时,
此时要十分注意,%d
的意义是将数据视作有符号整型(signed int
/int
)的补码,并以有符号十进制整数的格式输出。
故而此时还会发生整型提升:
c
整型提升后为:11111111 11111111 11111111 10000010
,是补码格式。
转换为原码:00000000 00000000 00000000 01111110
。
转换为有符号十进制形式:-126。
注意:
更多占位符的实际意义详见:标准输出函数与占位符
其实这里有个小tip:
以signed char
类型数据的运算为例子,其实际结果的范围如下:
-128、-127…-1、0、1、…126、127、-128、-127…-1、0、1、…126、127…
这是一个循环(实际上就是模运算),当数值超过127后,就会回到-128;当数值低过-128后,就会攀升到127。
3.3、算数转换
整型提升是指在整型家族的数据类型在参与算数运算时,量级小于或等于int
的数据类型都会提升至int
类型的精度后,再进行运算;它针对的是量级<=int
的整型家族的数据类型。
算数转换是指如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。它针对的是量级>=int
的各种数据类型(不局限于整型家族)。
3.3.1、算数转换规则
数据类型的量级排行:char
、unsigned char
、short
、unsigned short
、int
、unsigned int
、long
、unsigned long
、long long
、unsigned long long
、float
、double
、long double
。
当发生表达式求值时,若参与运算的操作数是不同的类型,则数据类型的量级较小的那个操作数会将自己的数据类型转换为与数据类型的量级较大的那个操作数相同的数据类型后,再发生运算。
3.3.2、整型提升和算数转换的区别
给出一个例子来体会:
char a = 4;
long long int b = 8;
printf("%lld\n",sizeof(a + b));
1、整数字面量4的二进制形式为:00000000 00000000 00000000 00000100
。
2、a
初始化进行赋值时发生截断:00000100
。
3、整数字面量8的二进制形式为:00000000 00000000 00000000 00001000
。
4、b
初始化进行赋值时发生隐式类型转换,补长为:00000000 00000000 00000000 00000000 00000000 00000000 00000000 00001000
。
5、a+b
表达式求值时,a
先发生整型提升:00000000 00000000 00000000 00000100
;又因为参与运算的操作数的类型依然不同,于是a
再发生算数转换:00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000100
;最终发生运算,得到结果为:00000000 00000000 00000000 00000000 00000000 00000000 00000000 00001100
。sizeof()
括号内的表达式不会发生运算,sizeof()
的作用是预测括号内表达式的结果的数据类型量级,故而最终得到的量级为8
。