位运算的细节补充
#include<bitset>
int main() {
//因为可能输入的2进制数可能过大导致默认数据类型超过int所以强转int
int a{ (int)0b100100'01111111111111111111111111111111 };//100100+0+(31个1)
std::cout << a << std::endl;
std::cout << std::bitset<70>(a) << std::endl;//可以从a变量地址开始输出70位数据
return 0;
}//输出如下图示
解释:首先 a 变量数据在定义时根据二进制强转而来的,只会保留低位32位,然后这32位的最高位作为符号位。因此当我们从 a 变量地址开始输出70个二进制数据时只会有0+(31个1)。小端存储,低位数据低地址。
int a{ (int)0b100100'11111111111111111111111111111111 };//100100+(32个1)
而此时,更新一下 a 变量的定义后再运行发现输出结果发生了奇妙的变化
解释:无论如何,当类型转换来定义 a 变量时,都只会保留低32位的数据,即如上的32位的1数据。因此输出 a 变量时最高位符号位为1,按照补码解释数据取反加一得出输出 -1 。奇妙的变化就是不管前面定义时的100100,反转符号位为1时,总会在高位再补32位的1。此时我把变量 a 转成 long long类型去读取时,的的确确在内存变成了64个 1。
左移的边界细节
在最高位的 1 数据左移后没到达符号位时,即为*2
#include<iostream>
#include<bitset>
int main() {
int a{ (int)0b10111111111111111111111111111111 };//32位数据
a <<= 1;//a = a << 1;
std::cout << a << std::endl;
std::cout << std::bitset<33>(a) << std::endl;
return 0;
}
说明:在int类型的32位作用域下,如果左移,高位的1是丢弃,并不会左移到超出作用域。即我上面输出页面特意输出33位数据,其中最高33位是0,说明原来32位的1左移并没有影响到它。然后就是右边补0。【即符号位会被修改】
右移的边界细节
右移为/2,向下取整
#include<iostream>
#include<bitset>
int main() {
int a{ (int)0b10111111111111111111111111111111 };//32位数据
a >>= 1;//a = a << 1;
std::cout << a << std::endl;
std::cout << std::bitset<33>(a) << std::endl;
return 0;
}
说明:当a变量的最高32位处符号位为1时,右移则左边恒补1,因为最高位33位为1。【即符号位不会被修改】
int a{ (int)0b01111111111111111111111111111111 };
说明:当a变量的最高32位处符号位为0时,右移则左边恒补0,因为最高位33位为0。【即符号位不会被修改】
关于左移右移的反汇编情况
调试窗口反汇编可以明显看到图示
当我们普通运算时的指令和优化后shl,sar的左移右移指令是在汇编不同的。即如果我们*2/4/8会被优化为shl而不是imul,同理算除数时也会被看情况优化成sar右移当除数为2、4、8…
我觉得a*= 3;
如果被优化为 a = (a << 1) + a;
可能会更快吧
取反~
与&
或|
异或^
每位对齐逐位运算规则:相同为0,相异为1。
公式①N ^ 0 = 0 ^ N = N ②N ^ N = 0 ③ (a ^ b) ^ c = a ^ (b ^ c) ④ a ^ b ^ c = a ^c ^ b ⑤a ^ b = c; a ^ c = b; c ^ b = a;
位运算的积累运用
TODO:学到就继续补充更新
Ⅰ:交换两个数据而不产生额外空间且效率快。
int a = 5;
int b = 6;
a = a ^ b;//即a = c
b = a ^ b;//即c^b=a,b=c
a = a ^ b;//即c^a=b,a=b
Ⅱ:如支付宝余额5元,在服务端存一个较大的密钥,即每次更新余额时都拿余额和密钥异或得到一个校验值。如果余额异常修改则校验值不对。我这样想的方法是一个保证数据正确性的异或使用
Ⅲ:136. 只出现一次的数字 - 力扣(LeetCode)
class Solution {
public:
int singleNumber(vector<int>& nums) {
int res = 0;
for(int i = 0;i<nums.size();i++){
res^=nums[i];
}
return res;
}
};
解析:题目得知必定存在有一个数据只出现一次,其他数据必定出现偶次。如样例4,1,2,1,2。
具体代码原理即 res = 0^4^1^2^1^2 = 0^(1^1)^(2^2)^4 = 0^4 = 4。即必定可以移动结合使得相同的数值异或得0,最后只剩下存在一次和0异或。
Ⅳ:260. 只出现一次的数字 III - 力扣(LeetCode)
class Solution {
public:
vector<int> singleNumber(vector<int> &nums) {
unsigned int K = 0;
for (int i = 0; i < nums.size(); i++) {
K ^= nums[i];//x^y = K
}
unsigned int k = (K & -K);//(K & -K)=={K&(~K+1)}即小写k为大写K的最低位1的值;如K = 1010, k = 0010
int ans1 = 0;
int ans2 = 0;
for (int i = 0; i < nums.size(); i++) {
if ((nums[i] & k) == 0) {//相同
ans1 ^= nums[i];
} else if((nums[i] & k) == k){//不相同
ans2 ^= nums[i];
}
}
vector<int> ans;
ans.emplace_back(ans1);
ans.emplace_back(ans2);
return ans;
}
};
解析:同上一题一样,相同值的都会异或碰掉,那么此时只会剩下答案的x,y。对它们两的异或值K分析,即找到最低位的不同的意思是找到最低位的 1 (因为相异为1)【(K & -K)】。此时就可以通过这个特性分两个组
class Solution {
public:
bool isPowerOfTwo(int n) {
if(n<=0) return false;//数据异常特殊处理
unsigned long long RF1 = (n&-n);
if( RF1 == n) return true;
else return false;
}
};
解析:RF1 = (n&-n)即找到n的最低位的1,如果等于本身即最低位的1唯一一个且也是最高位的1则true。因为2的幂本质就是在二进制只存在一个数据位 1。
class Solution {
public:
bool isPowerOfFour(int n) {
if(n <= 0) return false;
if((n&-n) != n) return false;//先排除不是2的幂
if((n & 0xaaaaaaaa) == 0) return true;//或者if((n & 0x55555555) == n) return true;
return false;
}
};
解析:先通过上题的思路来处理不是2的幂的数据,然后分析4的幂的数据必然是数据位上的1唯一且在奇数位上,那我们取得它得最低位数据1并通过了2的幂的检测后,这个数据位1必定要使得在奇数位则把找出一个所有偶数位为1的数来与之&操作若算出来为0则表示数据1的的确确在奇数位上,反之错误。同理&上所有奇数位1的数据得到本身也是对的。