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

修改对象内的各个位称为位操作。我们可以将对象视为单个位的集合,而不是将它们视为保存单个值。当对象的各个位用作布尔值时,这些位称为位标志

为了定义一组位标志,我们通常会使用适当大小的无符号整数(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)开始。每个数字表示一个位位置

1
2
76543210  Bit position
00000101 Bit sequence

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
All the bits: 00001101
Bit 3 has value: 1
Bit 4 has value: 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 针对速度而不是内存节省进行了优化。 std::bitset 的大小通常是保存位所需的字节数,四舍五入到最接近的sizeof(size_t) ,在 32 位计算机上为 4 字节,在 64 位计算机上为 8 字节。因此, std::bitset<8>通常会使用 4 或 8 字节的内存,即使它在技术上只需要 1 字节来存储 8 位。因此,当我们想要方便而不是节省内存时,std::bitset 最有用。

std::bitset其他经常有用的成员函数:

  • size() 返回 bitset 中的位数。

  • count() 返回 bitset 中设置为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;
}

输出:

1
2
3
4
5
8 bits are in the bitset
3 bits are set to true
All bits are true: false
Some bits are true: true
No bits are true: false

2. 按位运算符

C++ 提供了 6 个位操作运算符,通常称为按位运算符:

操作符 符号 形式 操作描述
左移 << x << y x 的所有位向左移动 y 位
右移 >> x >> y x 的所有位向右移动 y 位
按位取反 ~ ~x x 的所有位取反
按位与 & x & y x 的每一位与 y 的每一位进行 AND 操作
按位或 | x | y x 的每一位与 y 的每一位进行 OR 操作
按位异或 ^ x ^ y x 的每一位与 y 的每一位进行 XOR 操作

按位左移(<<) 运算符将位向左移动。左操作数是要移动位的表达式,右操作数是要左移的整数位。示例:

1
2
3
0011 << 1 //结果为0110
0011 << 2 //结果为1100
0011 << 3 //结果为1000

按位右移(>>) 运算符将位向右移动。示例:

1
2
3
1100 >> 1 //结果为0110  
1100 >> 2 //结果为0011
1100 >> 3 //结果为0001

这是进行一些位移的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <bitset>
#include <iostream>

int main()
{
std::bitset<4> x { 0b1100 };

std::cout << x << '\n';
std::cout << (x >> 1) << '\n'; // shift right by 1, yielding 0110
std::cout << (x << 1) << '\n'; // shift left by 1, yielding 1000

return 0;
}

输出:

1
2
3
1100
0110
1000

按位非运算符 (~) 可能是所有按位运算符中最容易理解的。它只是将每个位从 0 翻转为 1,反之亦然。请注意,按位非的结果取决于数据类型的大小。示例:

1
2
~0100 //结果为1011 
~0100 //结果为1011

按位或(|) 的工作方式与其对应的逻辑或非常相似。然而,不是对操作数应用“或”来产生单个结果,而是对每个位应用“按位或” !例如,考虑表达式 0b0101 | 0b0110 的结果为0b0111

按位与(&) 的工作方式与上面类似

1
2
3
4
0 1 0 1 AND
0 1 1 0
--------
0 1 0 0

最后一个运算符是按位异或(^),也称为异或。对于操作数中的每一对位,当成对位中只有一个为 1 时,按位异或将结果位设置为 true1),否则为 false0)。换句话说,当成对的位不同(一个是 0,另一个是 1)时,按位异或将结果位设置为 true

1
2
3
4
0 1 1 0 XOR
0 0 1 1
-------
0 1 0 1

与算术赋值运算符类似,C++ 提供了按位赋值运算符,以便于轻松修改变量​​。

操作符 符号 形式 操作描述
左移赋值 <<= x <<= y 将 x 左移 y 位并赋值给 x
右移赋值 >>= x >>= y 将 x 右移 y 位并赋值给 x
按位或赋值 |= x |= y 将 x 与 y 按位或的结果赋值给 x
按位与赋值 &= x &= y 将 x 与 y 按位与的结果赋值给 x
按位异或赋值 ^= x ^= y 将 x 与 y 按位异或的结果赋值给 x

没有按位 NOT 赋值运算符。这是因为其他按位运算符是二元的,但按位 NOT 是一元的

示例:

1
2
3
4
5
6
7
8
9
10
11
#include <bitset>
#include <iostream>

int main()
{
std::bitset<4> bits { 0b0100 };
bits >>= 1;
std::cout << bits << '\n';

return 0;
}

输出:

1
0010

如果按位运算符的操作数是小于int的整型,这些操作数将提升(转换)为intunsigned int ,并且返回的结果也将是intunsigned int 。例如,如果我们的操作数是unsigned short ,它们将被提升(转换)为unsigned int ,并且操作结果将以unsigned int形式返回。

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

位掩码是一组预定义的位,用于选择后续操作将修改哪些特定位。最简单的一组位掩码是为每个位位置定义一个位掩码。我们使用 0 来屏蔽我们不关心的位,并使用 1 来表示我们想要修改的位。尽管位掩码可以是文字,但它们通常被定义为符号常量,以便可以为它们指定一个有意义的名称并轻松重用。由于 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

由于C++11不支持二进制文字,我们必须使用其他方法来设置符号常量。有两种很好的方法可以做到这一点。第一种方法是使用十六进制:

1
2
3
4
5
6
7
8
constexpr std::uint8_t mask0{ 0x01 }; // hex for 0000 0001
constexpr std::uint8_t mask1{ 0x02 }; // hex for 0000 0010
constexpr std::uint8_t mask2{ 0x04 }; // hex for 0000 0100
constexpr std::uint8_t mask3{ 0x08 }; // hex for 0000 1000
constexpr std::uint8_t mask4{ 0x10 }; // hex for 0001 0000
constexpr std::uint8_t mask5{ 0x20 }; // hex for 0010 0000
constexpr std::uint8_t mask6{ 0x40 }; // hex for 0100 0000
constexpr std::uint8_t mask7{ 0x80 }; // hex for 1000 0000

一种更简单的方法是使用左移运算符将一位移到正确的位置:

1
2
3
4
5
6
7
8
constexpr std::uint8_t mask0{ 1 << 0 }; // 0000 0001
constexpr std::uint8_t mask1{ 1 << 1 }; // 0000 0010
constexpr std::uint8_t mask2{ 1 << 2 }; // 0000 0100
constexpr std::uint8_t mask3{ 1 << 3 }; // 0000 1000
constexpr std::uint8_t mask4{ 1 << 4 }; // 0001 0000
constexpr std::uint8_t mask5{ 1 << 5 }; // 0010 0000
constexpr std::uint8_t mask6{ 1 << 6 }; // 0100 0000
constexpr std::uint8_t mask7{ 1 << 7 }; // 1000 0000

现在我们有了一组位掩码,我们可以将它们与位标志变量结合使用来操作我们的位标志。为了确定某个位是打开还是关闭,我们将按位与和相应位的位掩码结合使用:

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

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

std::uint8_t flags{ 0b0000'0101 }; // 8 bits in size means room for 8 flags

std::cout << "bit 0 is " << (static_cast<bool>(flags & mask0) ? "on\n" : "off\n");
std::cout << "bit 1 is " << (static_cast<bool>(flags & mask1) ? "on\n" : "off\n");

return 0;
}

输出:

1
2
bit 0 is on
bit 1 is off

要设置(打开)一个位(值为 1),我们使用按位或等于(运算符 |=)以及相应位的位掩码:

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

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

std::uint8_t flags{ 0b0000'0101 }; // 8 bits in size means room for 8 flags

std::cout << "bit 1 is " << (static_cast<bool>(flags & mask1) ? "on\n" : "off\n");

flags |= mask1; // turn on bit 1

std::cout << "bit 1 is " << (static_cast<bool>(flags & mask1) ? "on\n" : "off\n");

return 0;
}

输出:

1
2
bit 1 is off
bit 1 is on

要重置(清除)一位(值为 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
#include <cstdint>
#include <iostream>

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

std::uint8_t flags{ 0b0000'0101 }; // 8 bits in size means room for 8 flags

std::cout << "bit 2 is " << (static_cast<bool>(flags & mask2) ? "on\n" : "off\n");

flags &= ~mask2; // turn off bit 2

std::cout << "bit 2 is " << (static_cast<bool>(flags & mask2) ? "on\n" : "off\n");

return 0;
}

输出:

1
2
bit 2 is on
bit 2 is off

要切换(翻转)位状态(从 0 到 1 或从 1 到 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
#include <cstdint>
#include <iostream>

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

std::uint8_t flags{ 0b0000'0101 }; // 8 bits in size means room for 8 flags

std::cout << "bit 2 is " << (static_cast<bool>(flags & mask2) ? "on\n" : "off\n");
flags ^= mask2; // flip bit 2
std::cout << "bit 2 is " << (static_cast<bool>(flags & mask2) ? "on\n" : "off\n");
flags ^= mask2; // flip bit 2
std::cout << "bit 2 is " << (static_cast<bool>(flags & mask2) ? "on\n" : "off\n");

return 0;
}

输出:

1
2
3
bit 2 is on
bit 2 is off
bit 2 is on

std::bitset 支持全套位运算符。因此,尽管使用函数(test, set, reset, and flip)来修改各个位更容易,但如果需要,您也可以使用按位运算符和位掩码。按位运算符允许您一次修改多个位。

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
26
27
28
29
30
31
32
#include <bitset>
#include <iostream>

int main()
{
[[maybe_unused]] constexpr std::bitset<8> mask0{ 0b0000'0001 }; // represents bit 0
[[maybe_unused]] constexpr std::bitset<8> mask1{ 0b0000'0010 }; // represents bit 1
[[maybe_unused]] constexpr std::bitset<8> mask2{ 0b0000'0100 }; // represents bit 2
[[maybe_unused]] constexpr std::bitset<8> mask3{ 0b0000'1000 }; // represents bit 3
[[maybe_unused]] constexpr std::bitset<8> mask4{ 0b0001'0000 }; // represents bit 4
[[maybe_unused]] constexpr std::bitset<8> mask5{ 0b0010'0000 }; // represents bit 5
[[maybe_unused]] constexpr std::bitset<8> mask6{ 0b0100'0000 }; // represents bit 6
[[maybe_unused]] constexpr std::bitset<8> mask7{ 0b1000'0000 }; // represents bit 7

std::bitset<8> flags{ 0b0000'0101 }; // 8 bits in size means room for 8 flags
std::cout << "bit 1 is " << (flags.test(1) ? "on\n" : "off\n");
std::cout << "bit 2 is " << (flags.test(2) ? "on\n" : "off\n");

flags ^= (mask1 | mask2); // flip bits 1 and 2
std::cout << "bit 1 is " << (flags.test(1) ? "on\n" : "off\n");
std::cout << "bit 2 is " << (flags.test(2) ? "on\n" : "off\n");

flags |= (mask1 | mask2); // turn bits 1 and 2 on
std::cout << "bit 1 is " << (flags.test(1) ? "on\n" : "off\n");
std::cout << "bit 2 is " << (flags.test(2) ? "on\n" : "off\n");

flags &= ~(mask1 | mask2); // turn bits 1 and 2 off
std::cout << "bit 1 is " << (flags.test(1) ? "on\n" : "off\n");
std::cout << "bit 2 is " << (flags.test(2) ? "on\n" : "off\n");

return 0;
}

输出:

1
2
3
4
5
6
7
8
bit 1 is off
bit 2 is on
bit 1 is on
bit 2 is off
bit 1 is on
bit 2 is on
bit 1 is off
bit 2 is off

将我们的位掩码命名为“mask1”或“mask2”告诉我们正在操作哪个位,但并没有给我们任何该位标志实际用途的指示。最佳实践是为位掩码提供有用的名称,作为记录位标志含义的一种方式。这是我们可能编写的游戏的示例:

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
26
27
#include <cstdint>
#include <iostream>

int main()
{
// Define a bunch of physical/emotional states
[[maybe_unused]] constexpr std::uint8_t isHungry { 1 << 0 }; // 0000 0001
[[maybe_unused]] constexpr std::uint8_t isSad { 1 << 1 }; // 0000 0010
[[maybe_unused]] constexpr std::uint8_t isMad { 1 << 2 }; // 0000 0100
[[maybe_unused]] constexpr std::uint8_t isHappy { 1 << 3 }; // 0000 1000
[[maybe_unused]] constexpr std::uint8_t isLaughing { 1 << 4 }; // 0001 0000
[[maybe_unused]] constexpr std::uint8_t isAsleep { 1 << 5 }; // 0010 0000
[[maybe_unused]] constexpr std::uint8_t isDead { 1 << 6 }; // 0100 0000
[[maybe_unused]] constexpr std::uint8_t isCrying { 1 << 7 }; // 1000 0000

std::uint8_t me{}; // all flags/options turned off to start
me |= (isHappy | isLaughing); // I am happy and laughing
me &= ~isLaughing; // I am no longer laughing

// Query a few states
// (we'll use static_cast<bool> to interpret the results as a boolean value)
std::cout << std::boolalpha; // print true or false instead of 1 or 0
std::cout << "I am happy? " << static_cast<bool>(me & isHappy) << '\n';
std::cout << "I am laughing? " << static_cast<bool>(me & isLaughing) << '\n';

return 0;
}

这是使用 std::bitset 实现的相同示例:

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
26
27
#include <bitset>
#include <iostream>

int main()
{
// Define a bunch of physical/emotional states
[[maybe_unused]] constexpr std::bitset<8> isHungry { 0b0000'0001 };
[[maybe_unused]] constexpr std::bitset<8> isSad { 0b0000'0010 };
[[maybe_unused]] constexpr std::bitset<8> isMad { 0b0000'0100 };
[[maybe_unused]] constexpr std::bitset<8> isHappy { 0b0000'1000 };
[[maybe_unused]] constexpr std::bitset<8> isLaughing { 0b0001'0000 };
[[maybe_unused]] constexpr std::bitset<8> isAsleep { 0b0010'0000 };
[[maybe_unused]] constexpr std::bitset<8> isDead { 0b0100'0000 };
[[maybe_unused]] constexpr std::bitset<8> isCrying { 0b1000'0000 };


std::bitset<8> me{}; // all flags/options turned off to start
me |= (isHappy | isLaughing); // I am happy and laughing
me &= ~isLaughing; // I am no longer laughing

// Query a few states (we use the any() function to see if any bits remain set)
std::cout << std::boolalpha; // print true or false instead of 1 or 0
std::cout << "I am happy? " << (me & isHappy).any() << '\n';
std::cout << "I am laughing? " << (me & isLaughing).any() << '\n';

return 0;
}

这里有两个注意事项:

  • std::bitset 没有一个很好的函数来允许您使用位掩码查询位。因此,如果您想使用位掩码而不是位置索引,则必须使用按位与来查询位。

  • 我们使用 any() 函数,如果设置了任何位,该函数将返回 true,否则返回 false,以查看我们查询的位是否保持打开或关闭状态。

4. 在二进制和十进制表示之间转换整数

4.1 将二进制转换为十进制

二进制数字 0 1 0 1 1 1
数字值 128 64 32 16 8
= 总计 (94) 0 64 0 16 8 4 2 0

4.2 将十进制转换为二进制

方法一:

不断除以 2,并记下余数。二进制数是在最后由余数自下而上构建的。将 148 从十进制转换为二进制的过程(使用 r 表示余数):

1
2
3
4
5
6
7
8
9
10
148 / 2 = 74 r0  
74 / 2 = 37 r0
37 / 2 = 18 r1
18 / 2 = 9 r0
9 / 2 = 4 r1
4 / 2 = 2 r0
2 / 2 = 1 r0
1 / 2 = 0 r1

结果为:1001 0100

方法二:

同样以十进制数 148 为例。

  • 小于 148 的最大 2 次方是 128,所以我们从这里开始。

  • 148>=128 吗?是的,所以 128 位必须是 1。148 - 128 = 20,这意味着我们需要找到更多值 20 的位。

  • 20 >= 64 吗?不,所以64位必须是0。

  • 20 >= 32 吗?不,所以32位必须是0。

  • 20 >= 16 吗?是的,所以 16 位必须是 1。20 - 16 = 4,这意味着我们需要再找到值 4 的位。

  • 4>=8 吗?不,所以第8位必须是0。

  • 4 >= 4 吗?是的,所以第 4 位必须为 1。4 - 4 = 0,这意味着其余所有位都必须为 0。

用表格的形式:

二进制数 1 0 0 1 0 1 0 0
数字值 128 64 32 16 8
= 总计 (148) 128 0 0 16 0 4 0 0

当数字很小时(例如 8 位二进制数),这种方法对于人类来说非常容易。对于机器来说它也非常高效,因为每个位都需要比较、减法和赋值。

方法三:

此方法是使用整数除法的方法 2 的变体。再次以十进制数 148 为例。小于 148 的最大 2 次方是 128,所以我们从这里开始。

  • 148 / 128 = 1,还有一些余数。由于 1 是奇数,因此该位为 1。

  • 148 / 64 = 2,还有一些余数。由于 2 是偶数,因此该位为 0。

  • 148 / 32 = 4,还有一些余数。由于 4 是偶数,因此该位为 0。

  • 148 / 16 = 9 加上一些余数。由于 9 是奇数,因此该位为 1。

  • 148 / 8 = 18,还有一些余数。由于 18 是偶数,因此该位为 0。

  • 148 / 4 = 37,还有一些余数。由于 37 是奇数,因此该位为 1。

  • 148 / 2 = 74,还有一些余数。由于 74 是偶数,因此该位为 0。

  • 148 / 1 = 148,还有一些余数。由于 148 是偶数,因此该位为 0。

这种方法对人类来说不太好,因为它需要大量的除法。对于机器来说,它的效率也较低,因为除法是一种低效的操作。但用代码编写很容易,因为它不需要 if 语句。

4.3 二进制加法和补码

  • 0 + 0 = 0

  • 0 + 1 = 1

  • 1 + 0 = 1

  • 1 + 1 = 0, 将 1 移至下一列

有符号整数通常使用称为补码的方法来存储。在二进制补码中,最左边(最高有效)位用作符号位。 0 符号位表示数字是正数(或零),1 符号位表示数字是负数。有符号正数以二进制表示,就像无符号正数(符号位设置为 0)一样。负符号数以二进制形式表示为正数的按位取反加 1。

4.4 将十进制转换为二进制(二进制补码)

例如,我们如何用二进制补码表示 -5:

  • 首先我们找出5的二进制表示:0000 0101

  • 然后我们反转所有位:1111 1010

  • 然后我们加1:1111 1011

为什么要加1呢?考虑数字 0。如果负值简单地表示为正数的倒数,则 0 将有两种表示形式:0000 0000(正零)和 1111 1111(负零)。通过加 1,1111 1111 故意溢出并变成 0000 0000。这可以防止 0 有两种表示形式,并简化负数算术所需的一些内部逻辑。

4.5 将二进制(二进制补码)转换为十进制

要将二进制补码二进制数转换回十进制,首先查看符号位。如果符号位为 0,只需按照上面无符号数所示的方式转换数字即可。如果符号位为1,那么我们将这些位取反,加1,然后转换为十进制,然后使该十进制数为负(因为符号位最初为负)。例如,要将 1001 1110 从二进制补码转换为十进制数:

  • 给出:1001 1110

  • 反转位:0110 0001

  • 加1:0110 0010

  • 转换为十进制:(0 * 128) + (1 * 64) + (1 * 32) + (0 * 16) + (0 * 8) + (0 * 4) + (1 * 2) + (0 * 1 ) = 64 + 32 + 2 = 98 由于原始符号位为负,因此最终值为-98。

还有另一种方法更容易手动计算。在该方法中,符号位代表负值,所有其他位代表正值。

  • 给出:1001 1110

  • 转换为十进制:(1 * -128) + (0 * 64) + (0 * 32) + (1 * 16) + (1 * 8) + (1 * 4) + (1 * 2) + (0 * 1) = -128 + 16 + 8 + 4 + 2 = -98

4.6 为什么类型很重要?

考虑二进制值 1011 0100。这代表什么值?您可能会说 180,如果这是一个无符号二进制数,那么你是对的。但是,如果使用二进制补码存储该值,则它将是 -76。如果该值以其他方式编码,它可能完全是另外一种东西。那么C++如何知道是否将包含二进制1011 0100的变量打印为180或-76呢?变量的类型决定了变量的值如何编码为二进制以及如何解码回值。因此,如果变量类型是无符号整数,它就会知道 1011 0100 是标准二进制,并且应该打印为 180。如果变量是有符号整数,它就会知道 1011 0100 是使用二进制补码编码的(现在保证为C++20),并且应打印为 -76。

参考资料

Learn C++ – Skill up with our free tutorials