AzerothCore
Pages :

位与字节教程

简介

在计算机中,数字在内部以二进制表示。这意味着,当你为变量使用整数类型时,它在内部会被表示为 0 和 1 的求和或拼接。

Byte

你也可以这样看:

Hobbyte

布尔值也是如此!如果你有一个等于 1 的位,或者一个等于 true 的布尔值,那么这两个变量的值之间不会有任何区别。

虽然就值输出而言确实如此,但一个布尔值会占用整整一个字节。 这是因为你不能只借用 1 位,所以要定义一个布尔值(bool),你需要占用 1 个字节或 8 位。

你可能知道,一个单独的位代表一个 0 或一个 1。八个这样的位拼接在一起代表一个字节。一个字节的最大值是 255,即所有位的总和。每个位都有一个“值”,而所有位的总和就是我们所说的掩码(mask)。例如,第一个位的值是 1,第二个位的值是其右边位的值的 2 倍。

// 这相当于说 2^N = x,例如
2^0=1
2^1=2
2^2=4
2^3=8
2^4=16
2^5=32
2^6=64
2^7=128

/*
 * 你可以相应地将一个字节表示为 0 和 1 的序列。
 * 按照这种方式阅读,位的顺序是从右到左
 * <-----
*/
1   = 0 0 0 0 0 0 0 1
2   = 0 0 0 0 0 0 1 0
4   = 0 0 0 0 0 1 0 0
8   = 0 0 0 0 1 0 0 0
16  = 0 0 0 1 0 0 0 0
32  = 0 0 1 0 0 0 0 0
64  = 0 1 0 0 0 0 0 0
128 = 1 0 0 0 0 0 0 0

当一个数字是 4 字节长时,这意味着你的数字实际上使用了内部存储中的 32 位:

00000000 00000000 00000000 00000000

32 位整数数据类型可以保存 −2,147,483,648 到 2,147,483,647 范围内的整数值。你也可以将这种数据类型称为有符号整数(signed int)或无符号整数。无符号整数(unsigned int)意味着 32 位无符号整数数据类型可以保存 0 到 4,294,967,295 范围内的整数值。 无符号整数之所以总能比有符号整数存储更多的正值,是因为我们将有符号整数的负值用作正值,这样你可以在变量中存储的正值数量就翻倍了。

它们的用途是什么?

现在想象一下,你想创建一个基于多个值保存状态的程序:

// 存储该程序的当前状态
bool firstValue = true;
bool secondValue = false;

或者,使用单个整数变量,用其内部 8 位中的每一位来表示不同的真假值。

// 00000101
int maskValue = 5;

在上面,第一位为 true,代表第一个变量。第二位为 false,代表第二个变量。第三位为 true,依此类推……

这是一种非常紧凑的数据存储方式,用途广泛。

这就是位掩码(bit masking)发挥作用的地方。听起来很复杂,但实际上非常简单。

位掩码允许用户:

  1. 编辑字节中的特定位。
  2. 检查特定的位值是否存在。
  3. 对值应用掩码,在我们的例子中,这个值就是我们的状态 00000101,而掩码同样是一个二进制数,用来指示我们关心的位。在这个例子中,我们的掩码是数字 5。

二进制运算

按位与 "&"

对位进行操作与在两个变量之间设置条件非常相似。例如,假设我们有:

if (1 && !0)
  printf('This condition is true');

任何不为 0 的值都等同于 true。这意味着:

  • 1 是 true
  • !0 相当于说“非 false”,也就是说 !0 是 true
  • true 与 true 满足条件

这样我们就可以比较两个位的值,如果它们都为 true,我们就可以保留该位的值,并得到相应的最终结果:

255 &= 1

// 由于以下原因,255 将变成 1:
// 用 F - False(假)和 T - True(真)来比较 255 和 1 的位
(255) -> 1 1 1 1 1 1 1 1
(1)   -> 0 0 0 0 0 0 0 1
(1)   -> F F F F F F F T
         0 0 0 0 0 0 0 1
         
// 另一个示例:
值:   00000101
掩码: 00000100
---- AND ---------
结果: 00000100

// 在这个例子中,我们的值是 5
int MyState = 5;

// 我们想从该值中移除 1 个状态
MyState &= 4;

/* 这会将我们的 00000101 (5)
 * 变成 00000100 (4)
 */

上面的例子确实是使用位运算的糟糕示例,可能让你感到疑惑:为什么不直接从数字中减去这些值呢?

5-1 = 4

这比绕来绕去地比较字节或位更容易理解,也更符合人类语言的直觉。

在位运算中使用 & 的意义,往往伴随着另一个位运算字符,即 '~'。

~ 允许你反转位掩码,例如:

255 &= ~1; // 这会使 255 变成 254,因为:
(255) -> 1 1 1 1 1 1 1 1
(1)   -> 0 0 0 0 0 0 0 1 // 第一位是 true
(~1)  -> 1 1 1 1 1 1 1 0 // 反转 1 中的位 -> 254
(254) -> 1 1 1 1 1 1 1 0

想象这样一个位掩码: 1 0 0 0 0 0 0 0 (128)

如果这是一次整数运算,那么 128 - 64 = 64

然而,如果你试图从 128 中移除 64:

1 0 0 0 0 0 0 0 &= ~64

(128) 1 0 0 0 0 0 0 0
(64)  0 1 0 0 0 0 0 0
(~64) 1 0 1 1 1 1 1 1
---------------------
(128) 1 0 0 0 0 0 0 0

所以 128 不会改变,因为 64 已经是关闭(off)状态。

这就是为什么你经常会在文件中看到变量添加新标志、移除其他标志。


按位或 "|"

向位掩码添加新标志比移除它们更容易。

这个概念的运用方式与条件中的 OR 完全相同:


if (true || false)
  printf("This condition is true");
else if (true || true)
  printf("This condition is true");
else if (false || true)
  printf("This condition is true");
else if (false || false)
  printf("This condition is false");

就像两个条件一样,你可以比较两个位并得到一个新的位掩码,因为只要其中任意一个位为 true,该位的值就会变成 true。换句话说,如果你有 1 或 0,新的位掩码值将是 1,例如:

// 这里我们用 5 初始化 MyState,用位的语言来说就是 00000101
int MyState = 5;

// 然后我们可以用以下语法向它添加一个掩码
MyState |= 128;

// 由于以下逻辑,我们的最终结果会将 5 变成 133:
5   -   0 0 0 0 0 1 0 1
128 -   1 0 0 0 0 0 0 0
------- 结果 --------
133 -   1 0 0 0 0 1 0 1

按位异或 "^"

这个位运算符的工作方式与预期不同,可能很难找到它的用法。

想象有以下位掩码:

位掩码
0 1 0 0 1 1 1 0 78
0 0 1 1 0 1 0 0 52
F T T T T F T F XOR 结果
0 1 1 1 1 0 1 0 122

你可能对刚才发生的事情感到困惑。

其实,我们只是将第一个掩码中的位与第二个掩码中的位逐一比较。如果它们不同,那么最终值为 true。用程序表示就是这样:

// 比较掩码 1 和掩码 2 的值
// 如果它们不同,则 XOR 条件为 true
for (int i = 0; i < 8; ++i)
{
  if (bitmask_1[i] != bitmask_2[i])
    printf("This condition is true")
}

按位左移 "<<"

该位运算会将所有二进制值按指定次数 N 进行移位。让我们深入了解一下:

如果我们在二进制中有如下值 X:

0 0 0 0 0 0 0 1(即 1)

然后我们执行 x << 1,x 将变成

0 0 0 0 0 0 1 0

根据这个规则,它将所有位向左移动一位,并在右侧补一个 0。 x << 2 会将所有位移动 2 位,因此 x 将变成

0 0 0 0 0 1 0 0

// 将值 "1" 向左移动 "8-1" 次
1<<(8-1)

// (8-1) 等同于 7
1<<(7)

// 这会将该值 000000001 中的 1 向左移动 7 位。
// 所以结果是 128 或
10000000

结论

速查表取自此链接

字符 运算 描述
& 与(AND) 以两个数字作为操作数,对两个数字的每一位进行 AND 运算。仅当两个位都为 1 时,AND 的结果才为 1
| 或(OR) 以两个数字作为操作数,对两个数字的每一位进行 OR 运算。只要两个位中任意一个为 1,OR 的结果就为 1
^ 异或(XOR) 以两个数字作为操作数,对两个数字的每一位进行 XOR 运算。如果两个位不同,XOR 的结果为 1。
<< 左移 以两个数字为操作数,将第一个操作数的位向左移动,第二个操作数决定移动的位数。
>> 右移 以两个数字为操作数,将第一个操作数的位向右移动,第二个操作数决定移动的位数。
~ 非(NOT) 取一个数字并反转它的所有位

致谢

本文档参考了以下文章编写:

https://www.geeksforgeeks.org/bitwise-operators-in-c-cpp/

https://stackoverflow.com/questions/31575691/what-is-a-bitmask-and-a-mask