C++中的位运算符:与、或、异或详解
在C++编程中,位运算符是对整数类型的二进制位进行操作的运算符。它们直接操作数据的二进制表示,是底层编程和高性能计算中的重要工具。本文将详细介绍C++中的与(&)、或(|)、异或(^)等位运算符。
一、位运算符概述
C++提供了以下几种位运算符:
- 按位与(&)
- 按位或(|)
- 按位异或(^)
- 按位取反(~)
- 左移(<<)
- 右移(>>)
这些运算符只能用于整数类型(char, short, int, long, long long及其unsigned版本)。
二、各运算符详解
1. 按位与(&)
功能:对两个操作数的每一位执行逻辑与操作,只有两个对应位都为1时,结果位才为1。
真值表:
A & B = Result
0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1
示例代码:
int a = 5; // 0101
int b = 3; // 0011
int result = a & b; // 0001 (1)
常见用途:
- 掩码操作(提取特定位)
- 判断奇偶(n & 1)
- 清零特定位
2. 按位或(|)
功能:对两个操作数的每一位执行逻辑或操作,只要有一个对应位为1,结果位就为1。
真值表:
A | B = Result
0 | 0 = 0
0 | 1 = 1
1 | 0 = 1
1 | 1 = 1
示例代码:
int a = 5; // 0101
int b = 3; // 0011
int result = a | b; // 0111 (7)
常见用途:
- 设置特定位
- 合并位掩码
3. 按位异或(^)
功能:对两个操作数的每一位执行异或操作,当对应位不同时结果为1,相同时为0。
真值表:
A ^ B = Result
0 ^ 0 = 0
0 ^ 1 = 1
1 ^ 0 = 1
1 ^ 1 = 0
示例代码:
int a = 5; // 0101
int b = 3; // 0011
int result = a ^ b; // 0110 (6)
常见用途:
- 交换两个变量的值(不使用临时变量)
- 加密/解密
- 找出只出现一次的数字(其他数字都出现两次)
4. 按位取反(~)
功能:对操作数的每一位执行逻辑非操作,即0变1,1变0。
示例代码:
int a = 5; // 0101
int result = ~a; // 1010 (取决于整数位数,实际是补码表示)
注意:结果取决于整数类型和位数,因为高位也会被取反。
5. 位移运算符(<< 和 >>)
虽然这不是本文重点,但简单介绍:
- 左移(<<):将二进制位向左移动指定位数,低位补0
- 右移(>>):将二进制位向右移动指定位数,对于无符号数高位补0,有符号数取决于实现
三、实用技巧示例
1. 交换两个变量的值(不使用临时变量)
int a = 5, b = 3;
a ^= b;
b ^= a;
a ^= b;
// 现在a=3, b=5
2. 判断奇偶
bool isOdd = num & 1; // 如果为true则是奇数
3. 检查第n位是否设置
bool isSet = num & (1 << n); // 检查第n位(从0开始)
4. 设置第n位
num |= (1 << n); // 设置第n位为1
5. 清除第n位
num &= ~(1 << n); // 清除第n位(设为0)
6. 切换第n位
num ^= (1 << n); // 如果第n位是1则变为0,反之亦然
四、注意事项
运算符优先级:位运算符的优先级通常低于算术运算符,高于逻辑运算符。建议使用括号明确运算顺序。
符号位处理:对有符号数进行位操作时要小心符号位的处理,可能产生意外结果。
移位运算陷阱:
- 不要对负数进行左移(行为未定义)
- 不要移位超过或等于操作数的位数(行为未定义)
可读性:虽然位运算效率高,但可能降低代码可读性,建议添加适当注释。
五、性能考虑
位运算通常在CPU级别非常高效,因为它们直接映射到处理器指令。在性能敏感的代码中(如图形处理、加密算法、网络协议等),合理使用位运算可以显著提高性能。
六、总结
C++的位运算符提供了直接操作二进制位的能力,是底层编程和高性能计算的重要工具。理解并掌握这些运算符对于编写高效、紧凑的代码非常有帮助。然而,使用时也需要注意其陷阱和限制,特别是在处理有符号数和移位操作时。
希望本文能帮助你更好地理解和应用C++中的位运算符。如果你有任何问题或补充,欢迎在评论区留言讨论!
拓展
找出数组中只出现一次的数字
题目描述
给定一个整数数组 nums
,其中恰好有一个元素只出现一次,其余每个元素均出现两次。找出那个只出现一次的元素。
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4
数据范围
- 数组长度
n
满足:0 < n ≤ 4000
- 数组中的每个元素值
val
满足:0 ≤ val ≤ 4000
进阶要求
- 空间复杂度:
O(1)
- 时间复杂度:
O(n)
解题思路
方法一:哈希表法(不满足空间复杂度要求)
思路:
使用哈希表记录每个数字出现的次数,最后遍历哈希表找出只出现一次的数字。
复杂度分析:
- 时间复杂度:
O(n)
- 空间复杂度:
O(n)
(不满足进阶要求)
代码示例:
#include <unordered_map>
#include <vector>
int singleNumber(std::vector<int>& nums) {
std::unordered_map<int, int> count;
for (int num : nums) {
count[num]++;
}
for (auto& pair : count) {
if (pair.second == 1) {
return pair.first;
}
}
return -1; // 根据题目描述,不会执行到这里
}
方法二:数学法(不满足空间复杂度要求)
思路:
利用集合去重,计算 2 * (a + b + c) - (a + a + b + b + c) = c
。
复杂度分析:
- 时间复杂度:
O(n)
- 空间复杂度:
O(n)
(不满足进阶要求)
代码示例:
#include <unordered_set>
#include <vector>
#include <numeric>
int singleNumber(std::vector<int>& nums) {
std::unordered_set<int> set(nums.begin(), nums.end());
int sum_set = std::accumulate(set.begin(), set.end(), 0);
int sum_nums = std::accumulate(nums.begin(), nums.end(), 0);
return 2 * sum_set - sum_nums;
}
方法三:位运算(最优解)
思路:
利用异或运算的性质:
- 任何数和0异或都是它本身:
a ^ 0 = a
- 任何数和自身异或都是0:
a ^ a = 0
- 异或运算满足交换律和结合律
将所有数字进行异或运算,出现两次的数字会互相抵消,最终结果就是只出现一次的数字。
复杂度分析:
- 时间复杂度:
O(n)
- 空间复杂度:
O(1)
(满足进阶要求)
代码示例:
#include <vector>
int singleNumber(std::vector<int>& nums) {
int result = 0;
for (int num : nums) {
result ^= num;
}
return result;
}
位运算解法详细说明
为什么异或运算能解决这个问题?
异或运算有以下重要性质:
- 交换律:
a ^ b = b ^ a
- 结合律:
a ^ (b ^ c) = (a ^ b) ^ c
- 自反性:
a ^ a = 0
- 恒等性:
a ^ 0 = a
因此,对于数组 [a, b, a, c, b]
,计算过程为:
a ^ b ^ a ^ c ^ b
= (a ^ a) ^ (b ^ b) ^ c
= 0 ^ 0 ^ c
= c
边界情况处理
虽然题目保证有解,但实际编程中可以考虑以下边界情况:
- 空数组(根据题目描述
0 < n
,可以忽略) - 所有元素都出现两次(题目保证有一个只出现一次的元素)
其他可能的解法
方法四:排序法(不满足时间复杂度要求)
思路:
先对数组排序,然后遍历查找只出现一次的元素。
复杂度分析:
- 时间复杂度:
O(n log n)
(不满足进阶要求) - 空间复杂度:
O(1)
(原地排序)
代码示例:
#include <vector>
#include <algorithm>
int singleNumber(std::vector<int>& nums) {
std::sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size() - 1; i += 2) {
if (nums[i] != nums[i + 1]) {
return nums[i];
}
}
return nums.back();
}
总结
方法 | 时间复杂度 | 空间复杂度 | 是否满足要求 |
---|---|---|---|
哈希表法 | O(n) | O(n) | 否 |
数学法 | O(n) | O(n) | 否 |
位运算法 | O(n) | O(1) | 是 |
排序法 | O(n log n) | O(1) | 否 |
最优解:位运算方法,满足所有要求且实现简洁高效。