上一期 二进制·之其一
上一期我们讲到了与二进制有关的位操作符,使用它们,我们就可以解决下面的问题了
一、不创建第三个变量,完成两个整数a,b的交换
不使用位操作符,其实也可以解决这个问题,如下:
int a = 1145;//a1
int b = 666;//b1
a = a + b;//a2 = a1+b1
b = a - b;//b(换) = a2-b1 = a1+b1-b1 = a1
a = a - b;//a(换) = a2-b(换) = a1+b1-a1 = b1
但存在一个问题:倘若a,b很大,a2就存在溢出的风险
为了规避这个风险,可以利用^的特点:
也可以实现这个功能:
int a = 1145;//a1
int b = 666;//b1
a = a ^ b;//a2 = a1^b1
b = a ^ b;//b(换) = a2^b1 = a1^b1^b1 = a1
a = a ^ b;//a(换) = a2^b(换) = a1^b1^a1 = b1
可惜的是,这种方法也存在局限性,它只能交换整数,不过它真正的价值是给我们提供了解决问题的新思路。
二、将13的二进制序列的第五位改成1,输出十进制结果,再改回0
13的二进制序列(补码)为:00000000 00000000 00000000 00001011
要将它的第五位改成1,怎么办呢?
哎,我们可以想到 | 的特点:那我们就可以找到序列(补码):00000000 00000000 00000000 00010000,将它和13进行按位或操作,不就完成了吗?
那么这个序列怎么得到呢?显然,它就是十进制数16,但与其换算它,不如直观看成1<<4,(1的补码是00000000 00000000 00000000 000000001)也是一样的。
所以,将第五位改成1的代码就可以写成:
int a = 13;
a = a | (1<<4);
而到了改回去的时候,其实就是想把第五位改成0,实际是和前面完全相反的操作,我们可以使用按位与
代码为:
int a = 13;
a = a | (1<<4);
a = a & ~(1<<4);// ~(1<<4)是序列11111111 11111111 11111111 11101111
灰常好理解!
三、求一个整数存储在内存中的二进制中1的个数
有了前面的理解,想必这个问题你也能很快思考出解决问题吧(乐)
咳,直接说吧,众所周知,1的二进制是00000000 00000000 00000000 00000001,,如果这个整数num的最后一位是1,那么num&1就等于1(真),反之则等于0(假)。所以我们要做的,就是让num的二进制的每一位都走到最后一位来检测一下,循环就能轻松做到这个事情。
int num = 666;
int count = 0;
for(int i=0 ; i<32 ; i++)
{
if( (num>>i) & 1 )
count++;
}
这样,最后count的值就是我们想要的答案了。
这应该是大多数人能想到的方法了,但其实还有一种优化的方式,规避了这个方式必须循环32次的冗杂:
int num = 666;
int count = 0;
while(num)
{
num = num&(num-1);
count++;
}
num = num&(num-1)这个表达式,能把num的二进制的最靠右的1去掉,在num变成0之前,这个表达式能执行几次,就说明num中有几个1
举个栗子:
设n = 14(十进制数字)
以下数字都是省略前面的0的二进制
n = 1110
n-1 = 1101
n1 = n&(n-1) = 1100
n1-1 = 1011
n2 = n1&(n1-1) = 1000
n2-1 = 0111
n3 = n2&(n2-1) = 0
那么,n&(n-1)执行了三次,就说明n里有3个1。
这种方法是不是很好?是不是效率很高?可惜的是,我们大部分人都没办法自己想出来这个办法,对吧?
不过,今天你学到了,就可以悄悄记住,以后再遇到类似的问题直接使用,就很nice!
本篇完,感谢阅读,
敬请期待——长篇连载《指针》