与进制有关的操作符
1.原码,反码,补码
2.移位操作符
左移操作符 <<
右移操作符 >>
3.位操作符
按位与&
按位或 |
按位异或 ^
4.几道例题
1.原码,反码,补码
按照一个数的正负,直接写出它的二进制表示形式就是原码
正数的原码、反码、补码是相同的
负数的原码、反码、补码要经过计算的
反码:原码的符号位不变,其他位按位取反
补码:反码+1
内存中存储的形式是:补码的二进制,所以在参与移位时,移动后都是补码
int n = 10,整形占四个字节(32bit)
00000000000000000000000000001010 原码
00000000000000000000000000001010 反码
00000000000000000000000000001010 补码
int n = -10
10000000000000000000000000001010 原码
111111111111111111111111111111110101 反码
111111111111111111111111111111110110 补码
原码、反码、补码相互转换图:
这里可以看出:原码到补码与补码到原码的操作实际上是一样的
2.移位操作符
左移操作符 <<
左移操作符:将补码向左移动,在右补0
要注意:b = a<<1左移运算符不是直接将a左移,而是将左移后的值保存到b中,a的值是不变的
如果想让a的·值变:a = a<<1或a<<=1
int main() { //对正数的左移 int a = 10; int b = a << 1; printf("%d\n", a); printf("%d\n", b); }
00000000000000000000000000001010 -10的补码,进行左移
左移后的补码:00000000000000000000000000010100
输出结果:
int main() { //对负数的左移 int a = -10; int b = a << 1; printf("%d\n", a); printf("%d\n", b); }
1
a的补码:111111111111111111111111111111110110
111111111111111111111111111111101100 -b的补码
10000000000000000000000000010100 -b的原码
输出结果:
从10与-10的左移一位的结果中,可以看出左移一位有扩大2倍的作用
右移操作符 >>
右移操作符分为两种
1.算术右移(常见):右边丢弃,左边补原来的符号位
2.逻辑右移:右边丢弃,左边直接补0
右移的方式取决于编译器,但是常见的右移是算术右移
对于移位运算符,不要移动负数位,这个是标准未定义的。
例如:
int num = 10; num>>-1;//error 1
2
3.位操作符
位操作符有:
& -按位与
| -按位或
^ -按位异或
它们的操作数必须是正数,并且针对二进制计算
按位与&
对应的二进制有0就为0,只有对应的两个都为1时,才为1
int main() { int a = 3; //00000000000000000000000000000011 a的补码 int b = -5; //10000000000000000000000000000101 //11111111111111111111111111111010 //11111111111111111111111111111011 b的补码 int c = a&b; //00000000000000000000000000000011 a的补码 //11111111111111111111111111111011 b的补码 //00000000000000000000000000000011 c的补码 }
按位或 |
对应的二进制位,有1则位1,两个同时为0则为0
int main() { int a = 3; //00000000000000000000000000000011 a的补码 int b = -5; //10000000000000000000000000000101 //11111111111111111111111111111010 //11111111111111111111111111111011 b的补码 int c = a|b; //00000000000000000000000000000011 a的补码 //11111111111111111111111111111011 b的补码 //11111111111111111111111111111011 c的补码 }
按位异或 ^
对应的二进制位:相同位0,相异为1
int main() { int a = 3; //00000000000000000000000000000011 a的补码 int b = -5; //10000000000000000000000000000101 //11111111111111111111111111111010 //11111111111111111111111111111011 b的补码 int c = a^b; //00000000000000000000000000000011 a的补码 //11111111111111111111111111111011 b的补码 //11111111111111111111111111111000 c的补码 }
异或操作赋一些特点:
1.a^a = 0
2.0^a = a
3.异或支持交换律
与^有关的变态面试题
不能创建临时变量(第三个变量),实现两个数的交换
#include <stdio.h> int main() { int a = 10; int b = 20; a = a^b; b = a^b; a = a^b; printf("a = %d b = %d\n", a, b); return 0; }
首先让a = a^b,接着b = a^b,其实就是b = a^b =a ^ b ^b = a,a = a^ b = a^b ^a,也就完成了交换
4.几道例题
这里,我们先想一下怎么得到二进制数字中的最后一个数字?
我们想一下求十进制数字的每一位:以11为例,11%2 = 1得到个位数字,11/2得到取出个位后的数字
其实二进制求每一位上数字一样
使用取模运算,除法运算是一种方法,同时也可以使用操作符进行计算
实际上,用1与其他数字做按位与&就可以得到最后以为二进制数字,因为1的二进制最后一位为1,其余为0,&的规则是:有0就为0,都是1才为1,所以得到的数字前面各位都为0,而最后一位由原数字决定,它最后一位为0,结果就为0.为1,结果就为1
接下来,为了得到去掉最后一位的剩余数字,只需把数字向右移动1即可 11>>1
前面的基础比较容易理解,但是在做题时有的方法我们仍难以想到,所以我们接下来看几道题理解理解
1.求一个数的二进制形式中有多少个1
使用前面的取模除法运算:
//二进制中1的个数 int NumberOf1(int n) { int count = 0; while (n) { if (n % 2 == 1) //如果最后一位为1.count+1 { count++; } n = n / 2; } return count; }
这里的循环条件位n>o因为只要不是数字0,其余数字的二进制数字中都会有1,所以n>0时进行循环,如果退出循环来,就说明剩下的数字中一定没有1
使用&,>>
int NumberOf1(int n) { int count = 0; while (n>0) { int a = n & 1; if (a==1) { count++; } n = n >> 1; return count; }
前面两种算法计算正数都可以,但是负数就计算不了,负数连while循环都进不去,所以我们不能使用n>0作为循环条件
不论正数还是负数,都是4个字节大小,32个比特位,所以我们需将二进制循环遍历32次求出每位上的数字即可
int NumberOf1(int n) { int i = 0; int count = 0; for (i = 0; i < 32; i++) //循环32次,求出每位上的数字 { if (((n >> i) & 1) == 1) { count++; } } return count; }
4
((n >> i) & 1),将n右移i次,i从0开始,每次加一,所以n从右移1位开始,每次多移一位,因为左右移操作符不会改变原来n的值,所以每次向后移动i位,就巧妙地得到了i位上的数字,在&1就能得到每位上的数字
这里有一个几乎想不到的求法,用n = n&(n-1)求
所以计算n = n&(n-1)使用的次数,就可以得出1的个数
int NumberOd1(int n) { int count = 0; while(n) { n = n&(n-1); count++; } return count++; }
1
2
2.获取一个整数二进制序列中所有的偶数位和奇数位,分别打印出二进制序列
void print(int n) { int arr[32] = { 0 }; printf("奇数位:"); for (int i = 1; i < 32; i+=2) { printf("%d ", (n>>i)&1); arr[i] = (n >> i) & 1; } printf("\n"); printf("偶数位:"); for (int i = 0; i < 32; i += 2) { printf("%d ", (n >> i) & 1); arr[i] = (n >> i) & 1; } printf("\n"); for (int i = 31; i >=0; i--) { printf("%d", arr[i]); } }
这里的原理其实和上一题一样,只是分为奇数和偶数,这里仍是对32位都遍历,只是每次i增加2
i从1开始,每次增加2,这就是在遍历奇数位
i从0开始,每次增加2,这就是在遍历偶数位
3.两个int(32位)整数m和n的二进制表达中,有多少个位(bit)不同?
int compare(int a, int b) { int count = 0; for (int i = 0; i < 32; i++) { if (((a >> i) & 1) != ((b >> i) & 1)) { count++; } } return count; }
1
12
这里也是,用(n>>i)&1得到两个数字的各个位,然后进行比较,看是否相等
其实这里也可以使用按位异或操作符^,如果对应每位相同位0,相异为1,在去求a^b中1的个数
i
nt compare(int a, int b) { //也可以用异或解决 int tmp = a ^ b; //统计tmp里1的个数 int count = 0; for (int i = 0; i < 32; i++) { if ((tmp >> i) & 1 == 1) { count++; } } return count; }
最后,其实在二进制的一些运算中,十分有趣,不时会让人感觉妙啊·~~