hello,大家好,今天我为大家带来的是移位运算、位运算以及逻辑运算,为了方便大家理解和掌握这三个知识点,我会在文章中附上一系列的图解和练习题,Let is go !

文章目录

  • 原码、反码、补码
  • 移位操作符
    • 1、左移操作符
    • 2、右移操作符
  • 位操作符
    • 1、基本运算
    • 2、按位异或的几个结论
  • 相关练习题
    • 1、变态笔试题(重点)
    • 2、Google笔试题(重点)
    • 3、求二进制中不同位的个数
    • 4、打印整数的奇偶二进制位
  • 逻辑操作符
  • 相关练习题
    • 1、360笔试题(重点)
    • 2、笔试题变式1
    • 3、笔试题变式2

原码、反码、补码

我们知道计算机只能识别二进制数据,所以整数要存入计算机中就必须转化为相应的二进制数,同时整数在内存中的二进制表现形式有三种,分别是原码、反码和补码。在内存中,计算机对数据进行运算的时候用的都是整数的补码。

对于正整数来说:原码 = 反码 = 补码;

对于负整数来说:原码符号位不变、其他位按位取反得到反码;反码加1得到补码;

下面我们以5和-5举例:


移位操作符

1、左移操作符

< 向左移动数据的二进制位,高位舍弃,低位直接补0。

其实,二进制位向左移动一位,数据的值就会变为原来的2倍,所以我们可以通过一位来实现一个数的n次方。

2、右移操作符

右移和左移有一点区别,因为二进制位向右移动之后,由于最高位代表符号位,所以最高位补0还是补1影响一个数是整数还是负数。

右移分为逻辑右移和算术右移:逻辑右移,最高位直接补0;算术右移,最高位补原符号位。


位操作符

1、基本运算

按位与(&):对两个数的二进制位进行计算,如果对应位置的两个数的二进制位都为1,结果就为1;否则,结果就为0;

按位或(|):对两个数的二进制位进行计算,如果对应位置的两个数的二进制位有一个为1,结果就为1;如果两个都为0,结果就为0;

按位异或(^):对两个数的二进制位进行计算,如果对应位置的两个数的二进制位不同,结果就为1;相同,结果就为0;

2、按位异或的几个结论

  1. 两个相同的数异或的结果一定为0;

  2. 任何数与0异或都等于它自己;

  3. 异或满足交换律;

上面这三个结论证明很简单,只需要带一个数进去即可,所以这里不做赘述。


相关练习题

1、变态笔试题(重点)

不能创建临时变量(第三个变量),实现两个数的交换:

法一:求和相减

缺点:当 a 和 b 很大时,把 a+b 的结果放在 a 中,可能会导致a存放不下溢出而发生截断,从而损失精度

法二:重复异或(^)

这里就需要我们熟悉按位异或的三个结论了:相同两个数异或为0,0与任意数异或为任意数、异或满足交换律。


2、Google笔试题(重点)

编写代码实现:求一个整数存储在内存中的二进制中1的个数:

法一:循环便利

思路分析:我们以在十进制中求某一数字的数量来引入二进制中求1的个数;

我们可以观察到,要求一个十进制数n中某一位数字m的个数,只需要让n对10取模与m比较、再让n除以10,反复循环,直到n等于0即可 –> 由此我们推断出求一个整数存储在内存中的二进制中1的个数的求法:

代码实现

上面这种方法看似可以,但其实是有问题的,它不能求负数二进制中1的个数;

以-1举例:我们知道-1的二进制是全1,所以计算结果应该是32,但是如果我们用上面的方法求出来的结果会是0,因为在第一次循环中-1/2等于0会导致直接退出循环;

那我们这种方法是不是就不可用了呢?其实,经过优化之后也是可用的:我们可以num赋值给一个无符号整数u_num,这样如果输入的是一个负数的话,它在赋值的时候会发生隐式类型转换,使得u_num变成一个非常大的正数,然后我们直接对u_num求二进制1的个数即可

优化代码:

法二:移位相与

思路分析:我们知道一个数按位与上一个1得到的结果就是该数最低二进制位所代表的数,同时我们又知道右移操作符可以让一个数的任意二进制位来到最后一位;所以我们就可以利用右移+按位与,再配合循环来实现要求。

代码实现:

这里for循环的作用是让num的每一位二进制位都与1按位求与,如果求得的结果为1(即对应二进制位为1)就让count++;由于这是直接对内存中的二进制位进行操作,所以不用担心负数会不一样,但是我们可以看到,这种方法必须循环32次,那么有没有更优的方法呢?请看方法三。

法三:与n-1按位求与

思路分析:由于方法三很难想到,所以我们这里先给结论,然后再举例分析。

结论:循环的让num & (num-1),直到num为0时循环结束,下面我们举例来说明:

我们可以看到,这里我们只循环了四次就求出了结果,比起第二种方法,效率有了质的提升;其实这里每一次循环的效果是去掉最低位的一个1,所以这里的循环次数与二进制中1的个数相同

代码实现:

总结:求一个整数的二进制中1的个数这道题最开始其实是Google公司的一道笔试题,由于这道题十分经典(优化方法很nb),所以后面广泛的被各大公司拿来考察以及面试,如果你将来在面试的时候被问到这道提的时候能够从第一种方法中的有符号数一步一步优化为无符号数、再优化为第二种方法、最后优化为第三种方法,相信面试官会向你竖起大拇指!


3、求二进制中不同位的个数

编写代码实现:求两个数二进制中不同位的个数:(牛客网链接)

思路分析:我们学习了如何求一个数二进制中1的个数后这道题就变得很简单了,我们只需要让这两个数按位异或,那么异或得到的这个数的二进制中1的个数就是两个数二进制中不同位的个数;然后再用上面三种方法中的其中一种求出1的个数就可以了。

代码实现:


4、打印整数的奇偶二进制位

编写代码实现:获取一个整数二进制序列中所有的偶数位和奇数位,分别打印出二进制序列:

思路分析:对于打印一个整数的二进制位,我们可以也是使用移位操作符和位操作符,然后配合循环来实现;但是这里需要注意的是:屏幕上先打印的应该是二进制中的高位,这样才符合我们阅读数字的习惯,所以循环变量的初始值要设置为高。

代码实现:


逻辑操作符

逻辑操作符一共分为两类:逻辑与(&&) 和 逻辑或(||);

逻辑与:当两个条件都为真时,执行后面的语句;当两个条件有一个或者都为假时,语句不执行。

逻辑或:当两个条件有一个及以上为真时,执行后面的语句;当两个条件都为假时,语句不执行。

注意:逻辑操作符在特定情况下会发生”短路”,即当条件1 && 条件2,若条件1为假时,此时整个逻辑表达式直接为假,条件2将不会被执行;当条件1 || 条件2,若条件1为真时,此时整个逻辑表达式直接为真,条件2将不会被执行;这个是逻辑表达式在学校期末、校招、面试中的重要考点,一定要掌握,具体细节及考察形式在下面的题目中来进行说明。


相关练习题

1、360笔试题(重点)

请问下面程序输出的结果是什么:(大家可以先自己做一下再看解析)

大家看到这个答案是不是很疑惑,不要慌,接下来我来为大家一步一步分析:

首先,这里我们定义并初始化了 i、a、b、c、d这四个元素,然后 a、b、d搭配++运算符和逻辑运算符求值;

a++:这里是后置++,先使用后++,所以a++的返回值是0,返回之后a++变成1;

a++ && ++b:由于这里是逻辑与操作符,并且a返回0,那么由我们上面学习的逻辑操作符的”短路”现象,我们就可以知道,++b && d++这些语句根本不会被执行,所以最终只有a被++了一次,b、c、d保持不变


2、笔试题变式1

接下来我们看一道上面笔试题的变式:(先自己思考后再看答案哦)

这里和原题差不多,只不过是把逻辑与操作符变成了逻辑或操作符而已;

a++:这里是后置++,先使用后++,所以a++的返回值是0,返回之后a++变成1;

a++ || ++b:由于这里是逻辑或,所以a为假时整个逻辑表达式并不一定为假,会继续往后面执行

++b:这里是前置++,先++再使用,所以b++之后变为3,再返回3;

++b || d++:我们知道,前面语句执行后 a=1、b=3,因为a++ && ++b = 1,所以这句逻辑表达式就变为了 1 || d++,即这里又会发生短路现象,最后的d++不会被执行,所以最终a被++了一次,b被++了一次,c、d保持不变。


3、笔试题变式2

最后我们再来对这道笔试题进行一次变式:(注意先自己思考再看答案和解析)

这里和原题唯一不一样的地方就是a被初始化1,而不是0;

此时a++会先返回a的值为1,再让a++变为2;1不为0,逻辑与不会发生短路,代码继续往后执行

++b:先让b++变为3,再返回b的值为3;此时a++ && ++b 1 && 3 = 1;

所以a++ && ++b && d++ 1 && d++,不会发生短路,代码继续往下执行;d++,先返回4,再让d++变成6。

所以最终a、b、d均被++一次,c不变。