C++ 位运算和std::bitset

位运算概述

位运算是直接操作二进制位的运算,包括按位与按位或按位异或按位取反左移右移。它们的操作基于二进制,因此执行速度非常快,广泛应用于嵌入式开发、算法优化和性能敏感场景。

位运算符的种类

运算符名称描述
&按位与

两个位都为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;
}
知识点
  • 扩展应用
    • 快速计算:左移代替乘法,右移代替除法。
    • 清除低kx >> 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 & -xx & (x - 1)
    • 快速计算:移位操作代替乘除法。
  • 实际应用
    • 加密解密:异或操作。
    • 性能优化:在嵌入式和底层开发中减少分支和条件判断。

1. 什么是 std::bitset

std::bitset 是 C++ 标准库中提供的一个模板类,用于管理和操作固定大小的 位集合。每个位(bit)可以是 01,并支持位操作(如设置、清除、翻转、测试等)。它封装了底层的位操作逻辑,提供了更高的抽象和易用性。

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()检查是否至少有一位为 1b.any();
none()检查是否所有位均为 0b.none();
all()检查是否所有位均为 1b.all();

3.2 位操作

以下方法可用于修改或操作位集合中的单个位或所有位:

方法功能示例
set()将所有位设为 1,或将特定位设为 1b.set(2);b.set();
reset()将所有位设为 0,或将特定位设为 0b.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 longunsigned long longb.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. 注意事项

  1. 固定大小限制std::bitset<N> 的大小必须在编译期确定,无法动态调整。如果需要动态大小,可以使用 std::vector<bool>(注意:std::vector<bool> 是空间优化的特例)。
  2. 整数溢出问题to_ulong()to_ullong() 的结果不能超过目标类型的范围,否则会抛出 std::overflow_error
  3. 性能问题:在性能关键的代码中,直接位运算可能优于 std::bitset

总结:
std::bitset 是 C++ 标准库中强大的位操作工具,它封装了常见的位运算,提升了可读性和安全性,非常适合用于布尔状态管理、位掩码操作和状态压缩等场景。尽管存在固定大小和性能上的局限性,但在代码易用性和功能性方面具有显著优势。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值