详细讲解 —— 整型提升、操作符优先级(C语言初阶)

1、 隐式类型转换 —— 整型提升

表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。

为什么要进行整型提升

  1. C的整型算术运算总是至少以缺省整型类型的精度来进行的。

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

  3. 表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度,一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。

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

如何进行整型提升

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

//负数的整型提升
char c1 = -1;
10000000000000000000000000000001 —— 原码
11111111111111111111111111111110 —— 补码
11111111111111111111111111111111 —— 补码
变量c1的二进制位中只有8个比特位 —— 发生截断
11111111 —— char类型c1的储存
因为char为有符号char —— 所以整型提升的时候 —— 高位补充符号位(111111111111111111111111111111111 —— 整型提升后的结果
可以利用整型提升之后的结果进行运算 —— 注意:内存中储存的是补码

//正数的整型提升
char c2 = 1;
00000000000000000000000000000001 —— 原码、补码、反码
变量c2的二进制位中只有8个比特位 —— 发生截断
00000001 —— char类型c2的储存
因为char为有符号char —— 所以整型提升的时候 —— 高位补充符号位(000000000000000000000000000000001 —— 整型提升后的结果

//无符号数的整型提升,高位补0

整型提升的例子:

#include<stdio.h>
int main()
{
    char a = 3;
    //00000000000000000000000000000011 —— 原码、反码、补码
    //00000011 —— 截断之后的储存存值 —— 补码
    //00000000000000000000000000000011 —— 整型提升之后 —— 可进行运算
    char b = 127;
    //00000000000000000000000001111111 —— 原码、反码、补码
    //01111111 —— 截断
    //00000000000000000000000001111111 —— 整型提升之后
    char c = a + b;
    //00000000000000000000000010000010 —— 运算之后
    //10000010 —— 截断之后
    //11111111111111111111111110000010 —— 整型提升 —— 补码
    //11111111111111111111111110000001 —— 反码
    //10000000000000000000000001111110 —— 原码 —— -126
    printf("%d\n", c);  //打印值为-126
    return 0;
}

在这里插入图片描述

例题:

//实例1
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;
}
答案为:c
解析:
变量a为字符类型 —— 需要进行整型提升 —— a为10110110  —— 最高位为符号位(1)
—— 整型提升之后 —— 11111111111111111111111110110110 —— 整型提升之后不等于原值
变量b同理 —— 整型提升之后不等于原值
变量c为整型 —— 不需要进行整型提升 —— 等于原值。
//实例2
int main()
{
 char c = 1;
 printf("%u\n", sizeof(c));
 printf("%u\n", sizeof(+c));
 printf("%u\n", sizeof(-c));
 return 0;
}
答案为:1   4   4
分析:
第一个:c为char类型的变量,所以大小为1个字节
第二个:+ 为运算操作符 —— 如果运算就需要进行整型提升 —— 整型提升到int类型 —— 大小为4字节
第三个;和第二个同理

2、 算术转换

  1. 前面我们讲的整型提升是:int类型大小以下及int类型运算类型的转换。
  2. 那么如果是int类型大小以上的运算要怎么转化呢?
  3. 如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换
long double
double
float
unsigned long int
long int
unsigned int
int

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
具体是怎么转换的现在先不讲 —— 因为我们现在还不知道浮点型在内存中时怎么存储的。

3、 操作符的属性

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

  1. 操作符的优先级
  2. 操作符的结合性
  3. 是否控制求值顺序。

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

操作符优先级

操作符描述结合性是否控制求值顺序
( )聚组N/A
( )函数调用L - R
[ ]下标引用L - R
.访问结构成员L - R
->访问结构指针成员L - R
++后缀自增L - R
后缀自减L - R
!逻辑反R - L
~按位取反R - L
+单目,表示正值R - L
-单目,表示负值R - L
++前缀自增R - L
前缀自减R - L
*间接访问R - L
&取地址R - L
sizeof去其长度,以字节表示R - L
(类型)类型转换R - L
*乘法L - R
/除法L - R
%取余L - R
+加法L - R
-减法L - R
<<左移操作符L - R
>>右移操作符L - R
>大于L - R
>=大于等于L - R
<小于L - R
<=小于等于L - R
==等于L - R
!=不等于L - R
&位于L - R
^位异或L - R
\位与L - R
&&逻辑与L - R
\\逻辑或L - R
? :条件操作符N\A
=赋值R - L
+=以…加R - L
-=以…减R - L
*=以…乘R - L
/=以…除R - L
%=以…取余R - L
<<=以…左移R - L
>>=以…右移R - L
&=以…与R - L
^=以…异或R - L
\=以…或R - L
,逗号L - R

其中与 \ 代替 |(与) 操作符,因为 | 在markdown的表格中打不出来

其中的L - R表示的是从左到右运算。(left - right)

一些有问题表达式

表达式1

//表达式的求值部分由操作符的优先级决定。
a*b + c*d + e*f

注释:代码1在计算的时候 —— 由于*+的优先级高 —— 只能保证*的计算是比+早
 —— 但是优先级并不能决定第三个*比第一个+早执行。

有以下两种计算可能:

第一种:
a*b
c*d
a*b + c*d
e*f
a*b + c*d + e*f

第二种:
a*b
c*d
e*f
a*b + c*d
a*b + c*d + e*f

表达式2

c + --c;

注释:同上,操作符的优先级只能决定自减--的运算在+的运算的前面 
—— 但是我们并没有办法得知操作符的左操作数的获取在右操作数之前还是之后求值 
—— 所以结果是不可预测的,是有歧义的。

表达式3

int main()
{
 int i = 10;
 i = i-- - --i * ( i = -3 ) * i++ + ++i;
 printf("i = %d\n", i);
 return 0;
}

表达式3在不同编译器中测试结果:非法表达式程序的结果

表达式4

int fun()
{
     static int count = 1;
     return ++count;
}
int main()
{
     int answer;
     answer = fun() - fun() * fun();
     printf( "%d\n", answer);//输出多少?
     return 0;
}

答案为(在VS2019编译器中):2 - 3 * 4 = - 10
虽然在大多数的编译器上求得结果都是相同的。
但是上述代码 answer = fun() - fun() * fun(); 中我们只能通过操作符的优先级得知:先算乘法,再算减法。
函数的调用先后顺序无法通过操作符的优先级确定。

表达式5

#include <stdio.h>
int main()
{
 int i = 1;
 int ret = (++i) + (++i) + (++i);
 printf("%d\n", ret);
 printf("%d\n", i);
 return 0;
}

这段代码中的第一个 + 在执行的时候 —— 第三个++是否执行 —— 这个是不确定的 —— 因为依靠操作符的优先级和结合性是无法决定第一个 + 和第三个前置 ++ 的先后顺序。

在 VS2019 中和Linux环境中的结果不一样

在 VS2019 中
答案:12 4
过程:4 + 4 + 4 = 12

在Linux环境中
答案:10 4
过程:2 + 3 + 4 = 10

总结:

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

评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT技术博主-方兴未艾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值