位运算概述
位运算是直接操作二进制位的运算,包括按位与、按位或、按位异或、按位取反、左移和右移。它们的操作基于二进制,因此执行速度非常快,广泛应用于嵌入式开发、算法优化和性能敏感场景。
位运算符的种类
运算符 | 名称 | 描述 |
---|---|---|
& | 按位与 | 两个位都为1时结果为1,否则为0(有0必0) (补充一下逻辑与 && (有假必假)) |
| | 按位或 | 两个位有一个为1时,结果为1,否则为0(有1必1) (补充一下逻辑或 || (有真必真)) |
^ | 按位异或 | 两个位不同结果为1,相同结果为0 |
~ | 按位取反 | 将每一位取反,0变1,1变0 |
<< | 左移运算 | 将二进制位左移指定次数,低位补0,( // 左移1位相当于*2 // 左移Y位,相当于*2的Y次方) |
>> | 右移运算 | 将二进制位右移指定次数,符号位保留或填充( // 左移1位相当于/ 2 // 左移Y位,相当于/2的Y次方) |
1. 按位与 (&
)
核心概念
- 按位与是逐位比较,只有两位均为1时结果才为1。
- 常用于掩码操作、清除特定位等。
判断奇偶性
#include <iostream>
int main() {
int num1 = 8; // 0b1000
int num2 = 9; // 0b1001
// 判断奇偶性:最低位是否为1
// 0b1000
// 0b0001
// 0b0000
std::cout << (num1 & 1) << " (Even)" << std::endl; // 输出 0 (偶数)
// 0b1001
// 0b0001
// 0b0001
std::cout << (num2 & 1) << " (Odd)" << std::endl; // 输出 1 (奇数)
return 0;
}
知识点
x & 1 == 1
:奇数x & 1 == 0
:偶数- 扩展应用:
- 二进制位清零:
x & 0b11111110
将最低位清零。(最低位设置成0,有0必0)
- 二进制位清零:
2. 按位或 (|
)
核心概念
- 按位或是逐位比较,只要一位为1结果即为1。
- 常用于设置标志位、将某些位强制为1。
设置标志位
#include <iostream>
int main() {
int flags = 0b0000; // 所有标志位为0
int mask = 0b0100; // 标志位掩码:设置第3位
flags = flags | mask; // 设置第3位为1
// 0b0000
// 0b0100
// 0b0100
std::cout << "Flags after setting: " << flags << std::endl; // 0b0100 ---> 4
return 0;
}
知识点
- 扩展应用:
- 打开多个标志位:可以同时设置多个位,例如
flags | 0b1100
。(将想要设置的Bit位设置成1,有1必1就可以达到目的)
- 打开多个标志位:可以同时设置多个位,例如
3. 按位异或 (^
)
核心概念
- 按位异或是逐位比较,相同为0,不同为1。
- 具备交换律、结合律,常用于加密解密、变量交换等。
变量交换
#include <iostream>
int main() {
int a = 5, b = 9;
std::cout << "Before swap: a=" << a << ", b=" << b << std::endl;
{
void swap(int &a, int &b) {
if (a != b) { // 防止 a == b 导致清零
a = a ^ b;
b = a ^ b;
a = a ^ b;
}
}
}
// 变量交换
// 任何数和0异或运算还是他本身.
// 异或运算本身等于0.
// 异或运算满足交换律和结合律.
// 异或运算相等于二进制加法(并且是不带进位的二进制加法).
a = a ^ b; // Step 1: a = 5 ^ 9
b = a ^ b; // Step 2: b = 5 ^ (9 ^ 9) = 5
a = a ^ b; // Step 3: a = (5 ^ 5) ^ 9 = 9
std::cout << "After swap: a=" << a << ", b=" << b << std::endl;
return 0;
}
知识点
- 加密解密:
encrypted = plain ^ key
,解密用encrypted ^ key
。 - 扩展应用:
- 判断两个数是否相等:
if ((a ^ b) == 0)
。
- 判断两个数是否相等:
4. 按位取反 (~
)
核心概念
- 将每一位取反:1变0,0变1。
- 常与补码结合使用,用于求相反数。
求相反数
#include <iostream>
int main() {
int num = 7; // 0b0111 (二进制表示)
// ~num: 按位取反
// 0000 0111 (num)
// 1111 1000 (~num)
// 加 1,得到补码(即负数)
int neg_num = ~num + 1; // 1111 1000 + 1 = 1111 1001 (-7的补码)
// 补码取反 + 1 得到原码
// 0000 0110 + 1
// 0000 0111 ---> 7
std::cout << "Original: " << num << ", Negative: " << neg_num << std::endl;
return 0;
}
知识点
- 补码规则:
- 正数:原码、反码、补码相同。
- 负数:补码 = 反码 + 1。
- 扩展应用:
- 清除特定位:
x & ~mask
将掩码指定的位清零。
- 清除特定位:
5. 左移 (<<
) 和右移 (>>
)
核心概念
- 左移 (
<<
):- 将二进制左移,低位补0。
- 左移
k
位相当于乘以2^k
。
- 右移 (
>>
):- 将二进制右移,符号位不变。
- 右移
k
位相当于除以2^k
。
左移与右移
#include <iostream>
int main() {
int num = 4; // 0b100
std::cout << "Left shift: " << (num << 2) << std::endl; // 4 * 2^2 = 16
std::cout << "Right shift: " << (num >> 1) << std::endl; // 4 / 2 = 2
return 0;
}
知识点
- 扩展应用:
- 快速计算:左移代替乘法,右移代替除法。
- 清除低
k
位:x >> k
将低位移除。
6. 常见位运算技巧
判断2的幂
bool isPowerOfTwo(int x) {
return x > 0 && (x & (x - 1)) == 0;
}
清除最低的1
int clearLowestOne(int x) {
return x & (x - 1);
}
保留最低的1
int keepLowestOne(int x) {
return x & (-x);
}
低位全为1
int setLowBits(int n) {
return (1 << n) - 1; // n位全为1
}
总结与补充
- 核心操作:按位与、或、异或、取反、左移、右移。
- 关键技巧:
- 掩码提取和清除:
&
操作。 - 标志位控制:
|
和~
。 - 最低位操作:
x & -x
、x & (x - 1)
。 - 快速计算:移位操作代替乘除法。
- 掩码提取和清除:
- 实际应用:
- 加密解密:异或操作。
- 性能优化:在嵌入式和底层开发中减少分支和条件判断。
1. 什么是 std::bitset
?
std::bitset
是 C++ 标准库中提供的一个模板类,用于管理和操作固定大小的 位集合。每个位(bit
)可以是 0 或 1,并支持位操作(如设置、清除、翻转、测试等)。它封装了底层的位操作逻辑,提供了更高的抽象和易用性。
1.1 特点
- 固定大小:
std::bitset<N>
只能存储固定数量的位(N
是编译期常量)。 - 类型安全:提供了接口避免直接使用位运算符的复杂性。
- 高效:使用了紧凑的内存布局,适合管理布尔型状态。
- 功能丰富:支持字符串转换、计数、翻转等高级操作。
2. 如何使用 std::bitset
?
2.1 定义与初始化
std::bitset
的模板参数是位集合的大小,以下展示了不同的初始化方法:
#include <bitset>
#include <iostream>
int main() {
std::bitset<8> b1; // 默认初始化:全0 -> 00000000
std::bitset<8> b2(42); // 用整数初始化:42 = 00101010
std::bitset<8> b3(std::string("1010")); // 用字符串初始化:00001010
std::cout << b1 << " " << b2 << " " << b3 << "\n";
return 0;
}
输出:
00000000 00101010 00001010
3. std::bitset
的常用功能
std::bitset
提供了一系列方法来操作位集合,涵盖了几乎所有常见的位操作需求。
3.1 基本信息查询
以下方法用于获取 bitset
的基本属性或状态:
方法 | 功能 | 示例 |
---|---|---|
size() | 返回位集合的大小(位数 N )。 | b.size(); |
count() | 返回值为 1 的位数(即置位数量)。 | b.count(); |
test(pos) | 测试第 pos 位是否为 1 ,若越界则抛出异常。 | b.test(2); |
any() | 检查是否至少有一位为 1 。 | b.any(); |
none() | 检查是否所有位均为 0 。 | b.none(); |
all() | 检查是否所有位均为 1 。 | b.all(); |
3.2 位操作
以下方法可用于修改或操作位集合中的单个位或所有位:
方法 | 功能 | 示例 |
---|---|---|
set() | 将所有位设为 1 ,或将特定位设为 1 。 | b.set(2); 或 b.set(); |
reset() | 将所有位设为 0 ,或将特定位设为 0 。 | b.reset(2); |
flip() | 翻转所有位,或翻转特定位(0 ↔ 1 )。 | b.flip(2); 或 b.flip(); |
operator[] | 获取或设置特定位的值(类似数组操作)。 | b[2] = 1; |
#include <bitset>
#include <iostream>
int main() {
std::bitset<8> b(42); // 初始值 00101010
b.set(3); // 设置第 3 位为 1 -> 00111010
b.reset(1); // 将第 1 位设为 0 -> 00111000
b.flip(); // 翻转所有位 -> 11000111
std::cout << "Final bitset: " << b << "\n";
return 0;
}
输出:
Final bitset: 11000111
3.3 转换
std::bitset
支持与字符串和整数之间的相互转换:
方法 | 功能 | 示例 |
---|---|---|
to_ulong() / to_ullong() | 将 bitset 转换为 unsigned long 或 unsigned long long 。 | b.to_ulong(); |
to_string() | 将 bitset 转换为字符串(可自定义字符)。 | b.to_string('x', 'y'); |
#include <bitset>
#include <iostream>
int main() {
std::bitset<8> b("10101010");
// 转换为整数
unsigned long val = b.to_ulong();
std::cout << "As integer: " << val << "\n"; // 输出:170
// 转换为字符串
std::cout << "As string: " << b.to_string('x', 'y') << "\n"; // 输出:xyxyxyxy
return 0;
}
4. std::bitset
的应用场景
4.1 状态管理
std::bitset
可用于管理多个布尔状态,比如开关的开/关状态:
std::bitset<8> switches; // 8 个开关,初始全关
switches.set(3); // 打开第 3 个开关
switches.reset(1); // 关闭第 1 个开关
4.2 位掩码
用于权限系统或特定标志位的管理:
std::bitset<8> permissions(0b10101010); // 权限掩码
if (permissions.test(2)) {
std::cout << "Permission 2 granted.\n";
}
test() 方法主要用于查询 std::bitset 中某个特定位的状态(是否为 1)。它是一种方便的方式来访问和检查位掩码中的某个权限或标志位。
4.3 动态规划与图论
在算法中,std::bitset
可用于状态压缩或高效存储图的邻接矩阵:
std::bitset<16> dp; // 用于存储子集状态
dp.set(3); // 标记某状态已访问
4.4 数据序列化
可将位集合转换为字符串或整数以进行存储或传输:
std::string serialized = b.to_string();
5. std::bitset
与直接位运算的对比
特性 | 直接位运算 | std::bitset |
---|---|---|
灵活性 | 高,可自定义位宽 | 较低,需固定大小 |
可读性 | 差,代码复杂度高 | 高,接口易读 |
性能 | 快,无额外开销 | 稍慢,但差距不大 |
功能丰富性 | 依赖手工实现 | 提供丰富接口 |
6. 注意事项
- 固定大小限制:
std::bitset<N>
的大小必须在编译期确定,无法动态调整。如果需要动态大小,可以使用std::vector<bool>
(注意:std::vector<bool>
是空间优化的特例)。 - 整数溢出问题:
to_ulong()
或to_ullong()
的结果不能超过目标类型的范围,否则会抛出std::overflow_error
。 - 性能问题:在性能关键的代码中,直接位运算可能优于
std::bitset
。
总结:std::bitset
是 C++ 标准库中强大的位操作工具,它封装了常见的位运算,提升了可读性和安全性,非常适合用于布尔状态管理、位掩码操作和状态压缩等场景。尽管存在固定大小和性能上的局限性,但在代码易用性和功能性方面具有显著优势。