Cpp学习9 第 O 章 位运算

铁名_IronName Lv5

O.1 — 通过 std::bitset 进行位标志和位操作

在某些存储密集型场景下,为了提高存储效率,将 8 个独立的布尔值“打包”到一个字节中可能很有用。要实现这些功能,我们需要能够在位级别操作对象。
C++ 为我们提供了实现这一目标的工具。修改对象中的单个位称为位操作 。

Bit flags  位标志

我们不必将对象视为包含单个值,而是可以将对象中的每一位都视为一个独立的布尔值。当对象的各个位用作布尔值时,这些位被称为位标志 。

要定义一组位标志,我们通常会使用适当大小的无符号整数(8 位、16 位、32 位等,具体取决于我们有多少个标志),或者 std::bitset

1
2
3
#include <bitset> // for std::bitset

std::bitset<8> mybitset {}; // 8 bits in size means room for 8 flags

位操作是少数几个应该明确使用无符号整数(或 std::bitset )的情况之一。

给定一个比特序列,我们通常从右到左对比特进行编号,从 0 开始(而不是 1)。每个数字表示一个比特位置 。
76543210 Bit position
00000101 Bit sequence

通过 std::bitset操作位

std::bitset 提供了 4 个关键成员函数,可用于进行位操作:

  • test() 函数允许我们查询一个比特是 0 还是 1。
  • set() 允许我们打开一个位(如果该位已经打开,则此操作不会执行任何操作)。
  • reset() 允许我们关闭一个位(如果该位已经关闭,则此操作不会执行任何操作)。
  • flip() 函数允许我们将一个位值从 0 翻转为 1,反之亦然。
    这些函数都以我们要操作的 位的位置 作为它们的唯一参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <bitset>
#include <iostream>

int main()
{
std::bitset<8> bits{ 0b0000'0101 }; // we need 8 bits, start with bit pattern 0000 0101
bits.set(3); // set bit position 3 to 1 (now we have 0000 1101)
bits.flip(4); // flip bit 4 (now we have 0001 1101)
bits.reset(4); // set bit 4 back to 0 (now we have 0000 1101)

std::cout << "All the bits: " << bits<< '\n';
std::cout << "Bit 3 has value: " << bits.test(3) << '\n';
std::cout << "Bit 4 has value: " << bits.test(4) << '\n';

return 0;
}

给代码片段命名可以提高代码的可读性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <bitset>
#include <iostream>

int main()
{
[[maybe_unused]] constexpr int isHungry { 0 };
[[maybe_unused]] constexpr int isSad { 1 };
[[maybe_unused]] constexpr int isMad { 2 };
[[maybe_unused]] constexpr int isHappy { 3 };
[[maybe_unused]] constexpr int isLaughing { 4 };
[[maybe_unused]] constexpr int isAsleep { 5 };
[[maybe_unused]] constexpr int isDead { 6 };
[[maybe_unused]] constexpr int isCrying { 7 };

std::bitset<8> me{ 0b0000'0101 }; // we need 8 bits, start with bit pattern 0000 0101
me.set(isHappy); // set bit position 3 to 1 (now we have 0000 1101)
me.flip(isLaughing); // flip bit 4 (now we have 0001 1101)
me.reset(isLaughing); // set bit 4 back to 0 (now we have 0000 1101)

std::cout << "All the bits: " << me << '\n';
std::cout << "I am happy: " << me.test(isHappy) << '\n';
std::cout << "I am laughing: " << me.test(isLaughing) << '\n';

return 0;
}

在之后的课程里会展示 枚举器 如何创建更好的命名位集合。

std::bitset 的大小通常是存储这些位所需的字节数, 向上 取整到最接近的 sizeof(size_t) ,在 32 位机器上为 4 字节,在 64 位机器上为 8 字节。

查询 std::bitset

  • size() 返回位集中的位数。
  • count() 返回位集中设置为 true 位数。
  • all() 返回一个布尔值,指示是否所有位都设置为 true 。
  • any() 返回一个布尔值,指示是否有任何位设置为 true 。
  • none() 返回一个布尔值,指示是否没有位设置为 true 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <bitset>
#include <iostream>

int main()
{
std::bitset<8> bits{ 0b0000'1101 };
std::cout << bits.size() << " bits are in the bitset\n";
std::cout << bits.count() << " bits are set to true\n";

std::cout << std::boolalpha;
std::cout << "All bits are true: " << bits.all() << '\n';
std::cout << "Some bits are true: " << bits.any() << '\n';
std::cout << "No bits are true: " << bits.none() << '\n';

return 0;
}

O.2 — 位运算符

左移<< 右移>> 按位非~ 按位与& 按位| 按位异或^

0011 << 1 is 0110
0011 << 2 is 1100
0011 << 3 is 1000

1100 />/> 1 is 0110
1100 />/> 2 is 0011
1100 />/> 3 is 0001

运算符能够根据参数的类型改变其行为,这种能力利用了_运算符重载_ 这一特性

~0011 is 1100
~0000 0100 is 1111 1011

按位赋值运算符

<<= >>= &= |= ^=

警告

位运算符会将具有较窄整数类型的操作数提升为 int 或 unsigned int 。
operator~ 和 operator<< 对位宽敏感,根据操作数的宽度不同,可能会产生不同的结果。为了确保结果正确,在使用此类位运算之前,请先将结果 static_cast 回较窄的整数类型。

尽可能避免对小于 int 的整数类型进行位移操作。

O.3 — 使用位运算符和位掩码进行位操作

Bit masks  位掩码

位掩码是一组预定义的位,用于选择后续操作将修改哪些特定位。

C++14 支持二进制字面量,因此定义这些位掩码很容易:

1
2
3
4
5
6
7
8
9
10
#include <cstdint>

constexpr std::uint8_t mask0{ 0b0000'0001 }; // represents bit 0
constexpr std::uint8_t mask1{ 0b0000'0010 }; // represents bit 1
constexpr std::uint8_t mask2{ 0b0000'0100 }; // represents bit 2
constexpr std::uint8_t mask3{ 0b0000'1000 }; // represents bit 3
constexpr std::uint8_t mask4{ 0b0001'0000 }; // represents bit 4
constexpr std::uint8_t mask5{ 0b0010'0000 }; // represents bit 5
constexpr std::uint8_t mask6{ 0b0100'0000 }; // represents bit 6
constexpr std::uint8_t mask7{ 0b1000'0000 }; // represents bit 7

为位掩码赋予有意义的名称,以便记录位标志的含义。

对于大多数程序而言,使用位标志节省的内存量不足以弥补增加的复杂性。但对于包含成千上万甚至数百万个相似对象的程序来说,使用位标志可以显著降低内存占用。如果需要,这是一种值得掌握的优化技巧。

O.4 — 二进制和十进制表示之间的整数转换

有符号整数通常使用二进制补码法存储。在二进制补码法中,最左边(最高有效位)的位用作符号位。符号位为 0 表示该数为正数(或零),符号位为 1 表示该数为负数。
负有符号数用二进制表示为正数的按位取倒数,再加 1。
(哪来的计算机组成课程知识?)

  • 标题: Cpp学习9 第 O 章 位运算
  • 作者: 铁名_IronName
  • 创建于 : 2026-08-08 14:49:05
  • 更新于 : 2026-08-08 15:32:40
  • 链接: https://blog.ironname.top/2026/Cpp/Cpp学习9/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论