大家好,我是FLASH。这篇博客会为大家讲解C语言中表达式求值的那些事,如果大家喜欢我的作品,请留下你的一键三连,这也是对我最大的鼓舞
目录
1. 隐式类型转换
1.1 截断和整型提升
2. 算术转换
3. 操作符的属性
3.1 操作符优先级
3.2 一些问题代码
表达式求值的顺序一部分是由操作符的优先级和结合性决定。同样,有些表达式的操作数在求值的
过程中可能需要转换为其他类型。
2. 数据类型大于等于int类型时进行算术转换。
C的整型算术运算总是至少以缺省整型(int)类型的精度来进行的。
为了获得这个精度,表达式中的字符(char)和短整型(short )操作数在使用之前被转
换为普通整型,这种转换 称为整型提升。
整型提升的意义:
整形提升是按照变量的数据类型的符号位来提升的。
负数的整形提升:高位补充其符号位 1
char c1 = -1;
char类型的变量c1的二进制位(补码)中只有8个比特位:1111 1111
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:1111 1111 1111 1111 1111 1111 1111 1111
正数的整形提升:高位补充其符号位 0
char c2 = 1;
char类型的变量c2的二进制位(补码)中只有8个比特位:0000 0001
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:0000 0000 0000 0000 0000 0000 0000 0001
无符号整形提升,高位补0
整型提升是隐式,不经意间发生的,就好像从来都没有感知到它的存在一样,但确是的的确确
存在的。
上个例子中的c只要参与表达式运算,就会发生整形提升,+c是个表达式 ,就会发生提升,所以
sizeof(+c) 是4个字节。表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节,但是 sizeof(c)就
是1个字节。
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一
个操作数的类型,否则操作就无法进行。
下面的层次体系称为寻常算术转换,如果某个操作数的类型在下面这个列表中排名较低,
那么首先要转换为另外一个操作数的类型后执行运算。
复杂表达式的求值有三个影响的因素。
两个相邻的操作符执行的先后顺序取决于他们的优先级。如果两者的优先级相同,取决于他们的结
合性。
操作符 | 描述 | 用法示例 | 结果类型 | 结合性 | 是否控制求值顺序 |
() | 聚组 | (表达式) | 与表达 式同 |
N/A | 否 |
() | 函数调用 | rexp(rexp,...,rexp) | rexp | L-R | 否 |
[ ] | 下标引用 | rexp[rexp] | lexp | L-R | 否 |
. | 访问结构成员 | lexp.member_name | lexp | L-R | 否 |
-> | 访问结构指针成员 | rexp->member_name | lexp | L-R | 否 |
++ | 后缀自增 | lexp ++ | rexp | L-R | 否 |
-- | 后缀自减 | lexp -- | rexp | L-R | 否 |
! | 逻辑反 | ! rexp | rexp | R-L | 否 |
~ | 按位取反 | ~ rexp | rexp | R-L | 否 |
+ | 单目,表示正值 | + rexp | rexp | R-L | 否 |
- | 单目,表示负值 | - rexp | rexp | R-L | 否 |
++ | 前缀自增 | ++ lexp | rexp | R-L | 否 |
-- | 前缀自减 | -- lexp | rexp | R-L | 否 |
* | 间接访问 | * rexp | lexp | R-L | 否 |
& | 取地址 | & lexp | rexp | R-L | 否 |
sizeof | 取其长度,以字节 表示 |
sizeof rexp sizeof(类 型) |
rexp | R-L | 否 |
(类 型) |
类型转换 | (类型) rexp | rexp | R-L | 否 |
* | 乘法 | rexp * rexp | rexp | L-R | 否 |
/ | 除法 | rexp / rexp | rexp | L-R | 否 |
% | 整数取余 | rexp % rexp | rexp | L-R | 否 |
+ | 加法 | rexp + rexp | rexp | L-R | 否 |
- | 减法 | rexp - rexp | rexp | L-R | 否 |
<< | 左移位 | rexp << rexp | rexp | L-R | 否 |
>> | 右移位 | rexp >> rexp | rexp | L-R | 否 |
> | 大于 | rexp > rexp | rexp | L-R | 否 |
>= | 大于等于 | rexp >= rexp | rexp | L-R | 否 |
< | 小于 | rexp < rexp | rexp | L-R | 否 |
<= | 小于等于 | rexp <= rexp | rexp | L-R | 否 |
== | 等于 | rexp == rexp | rexp | L-R | 否 |
!= | 不等于 | rexp != rexp | rexp | L-R | 否 |
& | 位与 | rexp & rexp | rexp | L-R | 否 |
^ | 位异或 | rexp ^ rexp | rexp | L-R | 否 |
| | 位或 | rexp | rexp | rexp | L-R | 否 |
&& | 逻辑与 | rexp && rexp | rexp | L-R | 是 |
|| | 逻辑或 | rexp || rexp | rexp | L-R | 是 |
? : | 条件操作符 | rexp ? rexp : rexp | rexp | N/A | 是 |
= | 赋值 | lexp = rexp | rexp | R-L | 否 |
+= | 以...加 | lexp += rexp | rexp | R-L | 否 |
-= | 以...减 | lexp -= rexp | rexp | R-L | 否 |
*= | 以...乘 | lexp *= rexp | rexp | R-L | 否 |
/= | 以...除 | lexp /= rexp | rexp | R-L | 否 |
%= | 以...取模 | lexp %= rexp | rexp | R-L | 否 |
<<= | 以...左移 | lexp <<= rexp | rexp | R-L | 否 |
>>= | 以...右移 | lexp >>= rexp | rexp | R-L | 否 |
&= | 以...与 | lexp &= rexp | rexp | R-L | 否 |
^= | 以...异或 | lexp ^= rexp | rexp | R-L | 否 |
|= | 以...或 | lexp |= rexp | rexp | R-L | 否 |
, | 逗号 | rexp,rexp | rexp | L-R | 是 |
代码1:
a*b + c*d + e*f
代码1在计算的时候,由于*比+的优先级高,只能保证,*的计算是比+早,但是优先级并不能
决定第三个*比第一个+早执行。
代码2:
c + --c;
虽然这个代码的操作顺序可以确定且有且仅有一种:操作符的优先级只能决定自减--的运算在
+的运算的前面。但是我们并没有办法得知,+操作符的左操作数的获取在右操作数之前还是之后
求值,所以结果是不可预测的,是有歧义的。
代码3:
int main()
{
int i = 10;
i = i-- - --i * ( i = -3 ) * i++ + ++i;
printf("i = %d\n", i);
return 0;
}
不要写出非常复杂的表达式代码。
代码4:
int fun()
{
static int count = 1;
return ++count;
}
int main()
{
int answer;
answer = fun() - fun() * fun();
printf( "%d\n", answer);
return 0;
}
虽然该代码在大多数的编译器上求得结果都是相同的。但是还是存在一些问题经不起推敲:操
作符的优先级只能决定 * 比 - 先算,但无法决定表达式中的3个函数先调用哪个。
代码5:
#include
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\n", ret);
return 0;
}
简单看一下汇编代码后发现。这段代码中的第一个 + 在执行的时候,第三个++是否执行,这个
是不确定的,因为依靠操作符的优先级和结合性是无法决定第一个 + 和第三个前置 ++ 的先后顺
序。
总结:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是
存在问题的。