C++ STL bitset 位图

概述

bitset 是 C++ 标准模板库(STL)中的一个容器类,专门用于存储和操作固定大小的二进制位序列。它在内存使用和位操作性能方面非常高效。

基本特性

1. 头文件

复制代码
#include <bitset>

2. 模板声明

复制代码
template<size_t N> class bitset;
  • N:bitset 的大小(位数),在编译时确定

3. 特点

  • 固定大小,编译时确定

  • 每位存储一个二进制值(0 或 1)

  • 内存高效:每位只占 1 bit(理论值)

  • 支持各种位操作

创建和初始化

基本创建方式

复制代码
// 默认构造:所有位初始化为 0
bitset<8> b1;          // 00000000

// 使用整数初始化
bitset<8> b2(42);      // 00101010 (42的二进制)

// 使用字符串初始化
bitset<8> b3("101010"); // 00101010
bitset<8> b4("101010", 4); // 从字符串前4位初始化:00001010

// 使用字符串和偏移量
bitset<8> b5("10101010", 4, 3); // 从第4位开始取3位:00000101

常用操作

1. 访问元素

复制代码
bitset<8> bs("10101010");

// 使用 [] 运算符访问(不进行边界检查)
bool bit0 = bs[0];  // 0(最右位)
bool bit7 = bs[7];  // 1(最左位)

// 使用 test() 方法(进行边界检查)
bool bit = bs.test(0);  // 返回第0位的值
// bs.test(8);  // 如果位置无效会抛出 out_of_range 异常

// 访问所有位
for (size_t i = 0; i < bs.size(); ++i) {
    cout << bs[i];
}

2. 设置和重置位

复制代码
bitset<8> bs;

bs.set();          // 所有位设置为1:11111111
bs.set(2);         // 设置第2位为1
bs.set(2, 0);      // 设置第2位为0
bs.set(2, true);   // 设置第2位为true(1)

bs.reset();        // 所有位重置为0:00000000
bs.reset(2);       // 重置第2位为0

bs.flip();         // 所有位取反
bs.flip(2);        // 第2位取反

3. 查询状态

复制代码
bitset<8> bs("10101010");

bs.any();    // 是否有任何位为1? true
bs.none();   // 是否所有位都为0? false
bs.all();    // 是否所有位都为1? false
bs.count();  // 为1的位的数量:4
bs.size();   // 总位数:8

位运算操作

bitset 支持所有标准位运算:

复制代码
bitset<8> b1("11001100");
bitset<8> b2("10101010");

// 位运算
bitset<8> b_and = b1 & b2;  // 10001000
bitset<8> b_or = b1 | b2;   // 11101110
bitset<8> b_xor = b1 ^ b2;  // 01100110
bitset<8> b_not = ~b1;      // 00110011

// 移位运算
bitset<8> b_shift_left = b1 << 2;  // 00110000
bitset<8> b_shift_right = b1 >> 2; // 00110011

类型转换

复制代码
bitset<8> bs("10101010");

// 转换为字符串
string s = bs.to_string();       // "10101010"
string s2 = bs.to_string('o', 'x'); // "xoxoxoxo"

// 转换为整数
unsigned long ul = bs.to_ulong();   // 170
unsigned long long ull = bs.to_ullong(); // 170

线程安全性

std::bitset 本身是线程不安全的。

在C++标准库中,除非特别说明,否则大多数容器和工具都不是线程安 全的。

这意味着如果你在多线程环境中直接修改或访问同一个 std::bitset 对象,而没有使用适当的同步机 制(如互斥锁、条件变量、原子操作等),则可能会导致数据竞争和其他并发问题。

数据竞争是指两个或更多的线程并发访问同一个内存位置,并且至少有一个线程是写入操作,且这些线 程之间没有使用同步来协调这些访问。数据竞争会导致未定义的行为,包括但不限于程序崩溃、数据损坏 或不可预测的结果。

如果你需要在多线程环境中使用 std::bitset,你应该确保对它的访问是同步的。这可以通过以下几种方 式实现:

1)使用互斥锁(如 std::mutex)来保护对 std::bitset 的访问。在访问 bitset 之前锁定互斥锁,在访问 完成后释放锁。这可以确保在任何时候只有一个线程可以修改或读取 bitset。

2)使用原子操作(如 std::atomic)来安全地修改 bitset 中的单个位。但是,请注意,std::bitset 本身 并不提供原子操作,因此你可能需要将 bitset 转换为其他支持原子操作的数据结构,或者只使用 bitset 的 一部分,并确保对该部分的访问是原子的。

3)避免在多个线程之间共享 bitset 对象。相反,每个线程可以拥有自己的 bitset 副本,并在需要时与 其他线程交换或合并数据。这种方法可能会增加内存使用,但可以避免并发问题

实际应用示例

示例1:权限管理系统

cpp

复制代码
enum Permission {
    READ = 0,
    WRITE = 1,
    EXECUTE = 2,
    DELETE = 3,
    SHARE = 4
};

class UserPermissions {
private:
    bitset<5> permissions;  // 5种权限
    
public:
    void grant(Permission p) {
        permissions.set(p);
    }
    
    void revoke(Permission p) {
        permissions.reset(p);
    }
    
    bool hasPermission(Permission p) const {
        return permissions.test(p);
    }
    
    void toggle(Permission p) {
        permissions.flip(p);
    }
    
    void display() const {
        cout << "Permissions: " << permissions << endl;
        cout << "Read: " << hasPermission(READ) << endl;
        cout << "Write: " << hasPermission(WRITE) << endl;
        cout << "Execute: " << hasPermission(EXECUTE) << endl;
    }
};

示例2:质数筛选(埃拉托斯特尼筛法)

cpp

复制代码
vector<int> sieveOfEratosthenes(int n) {
    bitset<1000000> is_prime;
    is_prime.set();  // 初始假设所有数都是质数
    
    is_prime[0] = is_prime[1] = 0;  // 0和1不是质数
    
    for (int i = 2; i * i <= n; ++i) {
        if (is_prime[i]) {
            for (int j = i * i; j <= n; j += i) {
                is_prime[j] = 0;
            }
        }
    }
    
    vector<int> primes;
    for (int i = 2; i <= n; ++i) {
        if (is_prime[i]) {
            primes.push_back(i);
        }
    }
    
    return primes;
}

示例3:状态标志管理

cpp

复制代码
class SystemStatus {
private:
    bitset<8> status;
    
public:
    enum StatusFlag {
        NETWORK_CONNECTED = 0,
        DISK_READY = 1,
        MEMORY_OK = 2,
        CPU_IDLE = 3,
        BATTERY_LOW = 4,
        ERROR_STATE = 5,
        WARNING = 6,
        MAINTENANCE = 7
    };
    
    void setFlag(StatusFlag flag, bool value) {
        status.set(flag, value);
    }
    
    bool checkFlag(StatusFlag flag) const {
        return status.test(flag);
    }
    
    // 检查系统是否就绪(网络连接、磁盘就绪、内存正常)
    bool isSystemReady() const {
        bitset<8> readyMask;
        readyMask.set(NETWORK_CONNECTED);
        readyMask.set(DISK_READY);
        readyMask.set(MEMORY_OK);
        
        return (status & readyMask) == readyMask;
    }
    
    // 获取错误状态
    bool hasErrors() const {
        return status.test(ERROR_STATE);
    }
};

性能特点

优点:

  1. 内存效率高 :1位/元素,比 bool 数组(通常1字节/元素)更省内存

  2. 操作速度快:位操作在硬件级别非常快

  3. 线程安全:不同 bitset 对象可以安全地在不同线程中使用

限制:

  1. 固定大小:编译时必须知道大小

  2. 不能动态调整:不能像 vector 那样动态增长

  3. 大小限制:受模板参数类型限制

与替代方案的比较

vs vector<bool>

cpp

复制代码
// bitset - 固定大小,更高效
bitset<1000> bs;

// vector<bool> - 动态大小,但可能效率较低
vector<bool> vb(1000);

vs 整数位操作

cpp

复制代码
// 使用整数
unsigned int flags = 0;
flags |= (1 << 2);  // 设置第2位

// 使用 bitset(更安全,功能更丰富)
bitset<32> bs;
bs.set(2);

最佳实践建议

  1. 适用场景

    • 固定大小的位标志集合

    • 需要高效位操作的场景

    • 内存受限的环境

  2. 注意事项

    • 访问越界时,[] 运算符未定义行为,test() 会抛出异常

    • 转换为整数时注意溢出

    • 对于非常大的 bitset,考虑内存占用

  3. 性能提示

    • 批量操作通常比单个操作更高效

    • 考虑使用位运算而不是循环操作单个位

总结

bitset 是 C++ 中处理固定大小位集合的强大工具,特别适合需要高效位操作的场景。虽然它缺乏动态调整大小的能力,但其卓越的性能和内存效率使其在许多应用中成为首选,如状态标志管理、权限系统、算法实现(如质数筛选)等。

相关推荐
橘子真甜~2 小时前
Reids命令原理与应用1 - Redis命令与原理
数据库·c++·redis·缓存
钓鱼的肝2 小时前
GESP系列(3级)小杨的储蓄
开发语言·数据结构·c++·笔记·算法·gesp
float_六七2 小时前
行级与块级元素:核心区别与应用场景
开发语言·前端·javascript
唐装鼠2 小时前
Rust Cow(deepseek)
开发语言·后端·rust
抹除不掉的轻狂丶2 小时前
Java 日志框架完整指南:发展历史、核心组成与最佳实践
java·开发语言·python
lsx2024063 小时前
Bootstrap5 按钮组
开发语言
林涧泣3 小时前
使用Java输出HelloWorld
java·开发语言
一个不知名程序员www3 小时前
算法学习入门---C/C++输入输出
c语言·c++
lsx2024063 小时前
Perl 基础语法
开发语言