文章目录
一、移位操作符
注:以下关于位的操作符讲解都是在32位机器平台下进行的。
1.左移操作符
<<:左移操作符,也就是把所有的二进制位向左移动对应的位数。规则是:左边移出(丢弃),右边补0。例如:
#include <stdio.h>
int main()
{
int num = 10;
int n = num << 3;
printf("n = %d\n", n);
printf("num = %d\n", num);
return 0;
}
运行结果:
我们知道数据在计算机中都是以补码的形式来存储的,所以num==10在左移3位后得到的还是补码(00000000 00000000 00000000 01010000),又因为正整数的原反补码是一样的,所以正整数10左移三位后得到的二进制数转换成十进制数为80。且num的值是没有改变的,通过输出结果可以看出。
2.右移操作符
>>:右移操作符,把所有的二进制位向右移动对应的位数。右移分为逻辑右移和算术右移:
● 逻辑右移:左边用0填充,右边移出(丢弃)
● 算术右移:左边用原该值的符号位填充,右边移出(丢弃)
Ⅰ.算术右移
#include <stdio.h>
int main()
{
int num = 10;
int n = num >> 1;
printf("n = %d\n", n);
printf("num = %d\n", num);
return 0;
}
运行结果:
这里得到的运行结果是n==5,但是你看不出是逻辑右移还是算术右移,因为对于正整数,不管是逻辑右移还算术右移左边都是补0。我们再看一段代码:
#include <stdio.h>
int main()
{
int num = -1;
int n = num >> 1;
printf("n = %d\n", n);
printf("num = %d\n", num);
return 0;
}
运行结果:
通过上面的输出结果可以看出,num==-1右移一位最高位补的是1,所以这里是算术右移。在我使用的VS2022这个编译器上采用的就是算术右移(大部分编译器都是算术右移)。
Ⅱ.逻辑右移
逻辑右移就不用考虑符号位了,不管是正整数还是负整数逻辑右移最高位补的都是0。
-1和10通过逻辑右移一位后得到的还是补码,只是逻辑右移后最高位补的是0,不管是正整数还是负整数,符号位都变成了0,都会被当成正整数,而正整数的原反补码相同。以有符号十进制数打印出来的结果就是:2147483647和5。
注意:移位操作符的操作数只能是整数。
警告:对于移位运算符,不要移动负数位,这种是标准未定义的。(就像10>>-1,这种是未定义的)
3.移位的复合赋值运算符
我们讲过复合赋值运算符,就是相应的算术运算符结合赋值运算符的一种简化,那么对于移位操作符也是有复合赋值运算符的:
#include <stdio.h>
int main()
{
int num1 = 10;
int num2 = 20;
num1 >>= 1;
num2 <<= 1;
printf("num1 = %d\n", num1);
printf("num2 = %d\n", num2);
return 0;
}
运行结果:
上面的 num1>>=1 其实就是 num1=num1>>1 的意思,num2<<=1即num2=num2<<1。
二、位操作符
1.按位与(&)
通过一段代码来说明位与操作符
#include <stdio.h>
int main()
{
int n = -10;
int m = 5;
printf("%d\n", n & m);
return 0;
}
运行结果:
按位与就是将两个整数所有对应的二进制位进行逐一比较,同1则为1,有0则为0,得到的二进制数即为按位与后的结果。就像上面的 -10 & 5:(注意:整数在计算机中存放的是补码)
所以n(-10) & m(5)的结果就为00000000 00000000 00000000 00000100,换算成十进制的整数就为4。
2.按位或(|)
按位或就是将两个整数所有对应的二进制位进行逐一比较,同0则为0,有1则为1,得到的二进制数即为按位或后的结果。
#include <stdio.h>
int main()
{
int n = -7;
int m = 12;
printf("%d\n", n | m);
return 0;
}
运行结果:
所以n(-7) | m(12)的结果就为11111111 11111111 11111111 11111101,按位或得到的这个结果还是补码,最高位是符号位,换算成10进制的整数就为-3。
3.按位异或(^)
按位异或的意思就是:将两个整数所有对应的二进制位进行逐一比较,相同为0,相异为1。
#include <stdio.h>
int main()
{
int n = 9;
int m = -13;
printf("%d\n", n ^ m);
return 0;
}
运行结果:
所以n(9) ^ m(-13)的结果就为:11111111 11111111 11111111 11111010,按位异或得到的结果还是补码,换算成十进制的整数就为-6。
4.按位取反(~)
按位取反就是将整数的所有二进制位(bit):是1的变为0,是0的变为1(包括符号位)。注意:数据在计算机中存储的是补码。
#include <stdio.h>
int main()
{
int a = 17;
printf("%d\n", ~a);
return 0;
}
运行结果:
按位取反是位操作符中唯一的单目操作符。需要注意:按位取反是所有的二进制位都要取反(0变1,1变0),包括符号位。而原码变成反码的取反是符号位不变,数值位按位取反。
5.位的复合赋值运算符
同样的,对于位操作符也是有复合赋值运算符的:
#include <stdio.h>
int main()
{
int a = 10;
int b = 13;
int c = -5;
a &= 1;
printf("%d\n", a);
b |= 1;
printf("%d\n", b);
c ^= 1;
printf("%d\n", c);
return 0;
}
运行结果:
上面的a &= 1、b |= 1、c ^= 1意思分别是a = a&1、b = b|1、c = c^1。就是位操作符结合赋值操作符的简化。注意:对于上述位操作符的操作数必须是整数。
三、操作符的结合性和优先级
C语言的操作符有2个重要的属性:优先级、结合性。这两个属性决定了表达式求值的计算顺序。
1.优先级
优先级指的是:如果一个表达式包含多个运算符,哪个运算符应该优先执行。各种运算符的优先级是不一样的。
2 + 3 * 4
上面示例中,表达式2 + 3*4里面既有加法运算符(+),又有乘法运算符(*)。由于乘法的优先级高于加法,所以会先计算3*4,而不是先计算2+3。
2.结合性
如果两个运算符的优先级相同,优先级没办法确定要先计算哪个,这个时候就要看结合性了,则根据运算符是左结合,还是右结合,决定执行顺序。即当一个操作数两侧的运算符优先级相同时,要看操作数与操作符的结合顺序。大部分运算符是左结合(从左到右执行),少数运算符是右结合(从右到左执行),比如赋值运算符(=)。
5 * 4 / 2
上面示例中,* 和 / 的优先级是相同的,所以对于中间的操作数4就要看与操作符 * 和 / 的结合性了,因为 * 和 / 都是左结合性,按照从左往右的结合方向。所以4先和*结合,也就是先执行5*4的运算,再执行 /2 的运算。
3.操作符优先级表
下表整理了运算符的优先级和结合性:
文章若有不足的地方,欢迎小伙伴们在评论区指正哦!