一、移位操作符
<< >>
在介绍移位操作符前先学习一下二进制的表示方法,二进制表示方法有3种:
原码,反码,补码
正数原码,反码,补码相同
负数原码,反码,补码需要计算
例子:5的二进制码
0000 0000 0000 0000 0000 0000 0000 0101 原码
0000 0000 0000 0000 0000 0000 0000 0101 反码
0000 0000 0000 0000 0000 0000 0000 0101 补码
第一位数表示正负。0为正数,1为负数。
-5的二进制码
1000 0000 0000 0000 0000 0000 0000 0101 原码
1111 1111 1111 1111 1111 1111 1111 1010 反码 (符号位不变,其他位按位取反)
1111 1111 1111 1111 1111 1111 1111 1011 补码(最后一位+1)
整数在内存中存的是二进制的补码。回到移位操作符。
左移
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 5;
//5的补码 0000 0000 0000 0000 0000 0000 0000 0101
a = a<< 1;
printf("%d", a);
return 0;
}
上段代码将 5 的补码左移一位,再最后一位补上0
此时二进制补码变成了 2³+2=8+2=10 。
输出结果
10
这次对-5左移一位
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = -5;
//-5 的补码1111 1111 1111 1111 1111 1111 1111 1011
a = a<< 1;
printf("%d", a);
return 0;
}
打印时,将打印原码,因此会先将补码转成原码。正数因为源反补都相同,不需要再换算。
计算发现 -5左移1位的原码就是 - (2³+2)= -10 ,打印结果自然也是-10。接下来介绍右移,右移分为算术右移和逻辑右移。
右移
算术右移:右边丢弃,左边补符号位。
逻辑右移:右边丢弃,左边补0 。
+5 -5 的原反补码及 对补码算术右移1位并转为原码
截图时截少了,左边的0没截到
代码:
int a = -5;
a = a >> 1;
printf("%d", a);
结果:
-3
右移运算是算术还是逻辑右移,是取决于编译器的,此次结果表明当前编译器采用的是算术右移。
不是-2的结果也说明不能用移位运算取代 "/2" 运算。
注意:无法移动负数位,标准里未定义。
a>>>-1 //error
二、 位操作符
| & ^ ~
&按位与:类似且的逻辑,二进制按位运算,前后两个运算数同时为“1”得“1”。
| 按位或:前后两个运算数只要其一为“1”,结果就为“1”。
^按位异或:前后两个运算数相同为0,相异为1。
~按位取反:对一个数的二进制按位取反。
& 按位与
程序
int a = -3;
int b = 5;
int c = a & b;
printf("%d", c);
同样先算出a,b的补码
a=-3,b=5
a 10000000000000000000000000000011 原
a 11111111111111111111111111111100 反
a 11111111111111111111111111111101 补
b 00000000000000000000000000000101 补
按位与 a&b,每位两者都为1,才得1,否则0, 得:
a 11111111111111111111111111111101 补
b 00000000000000000000000000000101 补
c 00000000000000000000000000000101 补
因为打印时会打印原码,这里再将补码转为原码,即:
因为c为正数所以正反补都相同
c 00000000000000000000000000000101 补
c 00000000000000000000000000000101 反
c 00000000000000000000000000000101 原
运行结果
5
| 按位或
同理
程序
int a = -3;
int b = 5;
int c = a | b;
printf("%d", c);
运行结果
-3
^ 按位异或
相同为0,相异为1。
程序
void test4() {
int a = -3;
int b = 5;
int c = a ^ b;
printf("%d", c);
}
运算过程
a=-3,b=5
a 10000000000000000000000000000011 原↓
a 11111111111111111111111111111100 反↓
a 11111111111111111111111111111101 补
b 00000000000000000000000000000101 补
c 11111111111111111111111111111000 补//异或运算:相同为0,相异为1
↓ 转原码打印
c 11111111111111111111111111110111 反
c 10000000000000000000000000001000 原//对应十进制的-8
运行结果
-8
异或特殊的两种用法,任何数与0异或是它本身;任何数与它本身异或是0。
程序
int a = 3;
printf("%d\n", a ^ 0);
printf("%d", a ^ a);
运行结果
3
0
运算过程
相同取0 相异取1
a 000000000000000000000000000000000011 //原
a 111111111111111111111111111111111100 //反
a 111111111111111111111111111111111101 补
0 000000000000000000000000000000000000 补
a^0 111111111111111111111111111111111101 补
a 111111111111111111111111111111111101 补
a 111111111111111111111111111111111101 补
a^a 000000000000000000000000000000000000 补
可以发现a^0=a,a^a=0 。由此又延伸出一种很常用的算法,异或一组数据就能得到其中不成对的数字。
对1234321这组数据异或
1^2^3^4^3^2^1=4
成对的数字相抵消为0,最后剩下一个4与0异或,就是4本身。
这里能听懂,那这道题对你来说那必是轻而易举(~ ̄▽ ̄)~力扣
有了这样的基础,你甚至能拿两个变量做更nb的操作——无需第三个变量就能交换两个变量的值。
int a = 3;
int b = 5;
a = a ^ b;
b = a ^ b;//此时a = a ^ b;
a = a ^ b;//此时b=a^b^b =a,也就是a =a^b^a 最终等于b
printf("a=%d b=%d", a, b);
运行结果
a=5 b=3
当然这里也可以用求和的方式实现交换,但这种方法有缺陷的,当a ,b的值非常大时,他们的和就会溢出,最终得不到想要的值,算权宜之计。
~ 按位取反
将补码全部取反,打印的时候同样要转换成原码打印。
1.下面的程序将二进制某位从0变成1又从1变成0,1变成0的运算用到“~”取反,具体过程看注释。
000000000000000000000000000000001011
↓ ↑目标位
000000000000000000000000000000001111
↓ ↑目标位
000000000000000000000000000000001011
程序
//~取反
//000000000000000000000000000000001011
int a = 11;
a |= (1 << 2);//对1左移2位
//000000000000000000000000000000001011 a
//000000000000000000000000000000000100 (1 << 2)
//000000000000000000000000000000001111 进行位或运算得到结果
printf("变形:%d\n", a);
a &= ~(1 << 2);
//000000000000000000000000000000001011 a
//111111111111111111111111111111111011 ~(1 << 2)
//000000000000000000000000000000001011 进行位与运算得到结果
printf("还原:%d\n", a);
运行结果
变形:15
还原:11
2. 可以用来终止获取输入 while(~scanf() )
scanf 读取失败时返回 -1,而-1的补码是32/64位1,取反后为0,即返回假,所以条件终止。
-1 1111 1111 1111 1111 1111 1111 1111 1111
0 0000 0000 0000 0000 0000 0000 0000 0000
-看到这里应该懂了8,希望本文能帮助你理解位、移位操作符
-什么?你说还不懂(*゜ー゜*)
-欢迎在评论区留言问题~