📅  最后修改于: 2023-12-03 15:22:06.467000             🧑  作者: Mango
位操作是对数据进行操作的一种强大而高效的方法,程序员必须掌握它们,因为在很多情况下它们是最优解决方案。在本文中,我们将介绍位操作及其常用技巧和应用。
位操作是指对二进制位进行的操作。在计算机中,每个数据都是由比特(bit)组成,每个比特的取值为0或1。位操作的目的是在二进制位级别上实现数据操作,包括移位、与运算、或运算、异或运算等等。
位移操作是将二进制数向左或右移动指定的位数。对于正数,左移相当于乘以2的左移次幂,右移相当于除以2的右移次幂。对于负数,则需要使用算术位移(如C++中的“>>”),以保持符号不变。
int x = 0b101; // 二进制数101
x <<= 2; // 左移2位,等价于x *= 4
std::cout << x << std::endl; // 输出1000,即二进制数1000
int y = -0b101; // 负的二进制数101
y >>= 2; // 算术右移2位,等价于y /= 4
std::cout << y << std::endl; // 输出-2,即负的二进制数10
位掩码是一种常见的位操作技巧,它用来提取或设置二进制数中的某些位。位掩码通常是一个由1和0组成的二进制数,其中1表示要提取或设置的比特,0表示不关心的比特。
// 提取二进制数中的第3和第4位,其余位都清零
int mask = 0b1100;
int x = 0b1011;
x &= mask; // 与操作后,x是二进制数1000
std::cout << x << std::endl; // 输出8,即二进制数1000
// 设置二进制数中的第3和第4位为1,其余位不变
int mask = 0b1100;
int x = 0b1011;
x |= mask; // 或操作后,x是二进制数1111
std::cout << x << std::endl; // 输出15,即二进制数1111
按位异或操作是指一个二进制数的每个比特与另一个二进制数的相应比特进行异或运算。异或运算的结果是如果两个比特不同,则结果为1,否则为0。
int x = 0b1011;
int y = 0b1100;
int z = x ^ y; // 异或操作后,z是二进制数0111
std::cout << z << std::endl; // 输出7,即二进制数111
位操作可以用来检查一个二进制数中是否存在某一比特。对于一个32位的整数变量,我们可以用一个32位的掩码(或称为位运算索引)来检查这个变量的每一个比特。
bool has_bit(int x, int bit) {
return (x & (1 << bit)) != 0;
}
// 判断二进制数1011(十进制数11)中的第1、2、3、4位是否为1
for (int i = 0; i < 4; ++i) {
if (has_bit(0b1011, i)) {
std::cout << "Bit " << i << " is set" << std::endl;
}
}
看到一个题目实例可以更好的理解位运算。例如,下面的题目:
给定一个长度为n的整数数组nums,其中仅有一个元素出现了一次,其他所有元素都出现了两次。找出那个只出现一次的元素。
使用位运算可以很容易地解决这个问题:对所有元素进行按位异或运算,最后的结果就是只出现一次的元素。这是因为每个出现两次的元素在异或运算中被消除了,而只有只出现一次的元素被保留下来。该算法的时间复杂度为O(n),空间复杂度为O(1)。
int single_number(std::vector<int>& nums) {
int res = 0;
for (int x : nums) {
res ^= x;
}
return res;
}
位操作在程序员的工作中是不可或缺的,掌握位操作技巧和应用可以为程序的实现和优化提供更多的可能性。在处理位操作时务必小心,特别是在移动比特和使用算术位移时要考虑符号的影响。虽然位操作可能会使代码更加复杂,但在很多情况下,它们是实现最高效算法的关键。