我正在学习计算机系统类中的按位运算符和屏蔽。但是我在内化它们时遇到了一些麻烦。
我理解运算符,&,|,^,>> (算术和逻辑移位)和<< DO,但除了优化乘法和除法运算(对于>>和<<),并且检查某些位是打开还是关闭(&&)时,我还没有得到它们真正用于它们的东西。操作者)。
另外,我不明白使用什么掩蔽。我知道做x& 0xFF用于提取整数x中的最低有效位,但我无法真正推断出其他种类的掩码(例如,提取数字中最左边的1,获得数字中的1的数量)等等)?
有人可以对此有所了解,最好是举一些例子吗?谢谢。
答案 0 :(得分:6)
了解位掩码的好方法是举个例子,所以我会给出一个。让我们说我们有一系列结构:
struct my_struct {
int foo;
int bar;
};
struct my_struct array_of_structs[64]; // I picked 64 for a specific reason I will discuss later
我们将此数组用作池,并且根据需要分配此数组的元素,也可以取消分配。实现此目的的一种方法是在结构中添加used
布尔成员。
struct my_struct {
int foo;
int bar;
char used;
};
但另一种方法是创建一个位图。由于数组大小为64,因此我们只需要一个64位整数。请注意,如果元素数多于单个数据类型中的位数,则可以使用位图元素数组执行此操作,但为了清楚起见,我将省略此内容。
unsigned long long bitmap; // Guaranteed to be at least 64 bits (if I recall correctly)
所以让我们让每一位都对应于我们数组中的一个元素,该位的0表示不使用,使用1表示。因此,要将元素i
标记为已使用,我们将执行以下操作:
bitmap = bitmap | (1ULL << i);
或者更简洁:
bitmap |= (1ULL << i);
除(1ULL << i)
位以外, i
的每一位都设置为0,因此bitmap | (1ULL << i)
与bitmap
相同,但i
位设置除外同样(不管以前是什么)。 bitmap |= (1ULL << i);
正在做的基本上是说我想将i
位设置为1并将其他所有内容保留原样。这里的i
位用于表示i
对象是否空闲,所以另一种解释方法是我想将i
元素标记为已使用。< / p>
现在要测试是否使用了元素i
,我们会使用&
:
if(bitmap & (1ULL << i)) {
// i is used
}
else {
// i is not used
}
bitmap & (1ULL << i)
只会是一个非零值,因此如果i
中的bitmap
位也设置为bitmap = bitmap & ~(1ULL << i);
,则为真。
最后,为了将元素标记为未使用,我们将执行以下操作:
bitmap &= ~(1ULL << i);
或再次更简洁
~(1ULL << i)
i
将为64位(假设无符号长long为64位),除了bitmap
位之外,每个位都设置为1。当使用bitmap
进行结果时,结果与i
完全相同,但used
位将设置为0。
有人可能想知道何时使用位图与struct player {
// some members
unsigned long long flag;
};
变量。在某些情况下,位图可能会更快,虽然它也可能更慢,我会说你必须测试哪些适用于你的应用程序,如果这部分成为真正的瓶颈。我可以给出一个使用位图来标记使用或不使用的东西的例子是当你没有自定义结构时。具体来说,根据我自己的经验,我在操作系统中使用位图来标记使用或未使用的物理帧。由于没有struct,因为我标记的是内存本身,位图可以工作。然而,就找到免费帧而言,这不是最有效的,但它有效。
另一个常见用途是标记属性或属性是否存在。这通常被称为位标志。例如,假设我们有一个带有标志元素的播放器结构。
#define PLAYER_JUMPING (1ULL << 0)
#define PLAYER_SWIMMING (1ULL << 1)
#define PLAYER_RUNNING (1ULL << 2)
#define PLAYER_DEAD (1ULL << 3)
然后我们可能会有关于这个玩家的各种属性,比如跳跃,游泳,跑步,死亡。我们可以创建与每个属性相对应的位掩码。
struct player my_player;
my_player.flag |= PLAYER_JUMPING; // Mark the player as jumping
my_player.flag &= ~PLAYER_SWIMMING; // Mark the player as not swimming
if(my_player.flag & PLAYER_RUNNING) // Test if the player is running
然后使用已经演示的类似操作来打开和关闭属性。
^
最后,我之前没有证明过的一个操作是按位异或:my_player.flag = my_player.flag ^ PLAYER_DEAD; // If player was dead now player is not dead and vise versa
。您可以使用它来切换属性。
my_player.flag ^= PLAYER_DEAD; // If player was dead now player is not dead and vise versa
或再次更简洁:
x ^ 0 == x
这将仅影响位掩码中设置的特定位,所有其他位将保留其先前的值,即位级别为if(my_player.flag & (PLAYER_RUNNING | PLAYER_JUMPING)) // Test if the player is running or jumping
。
当以这种方式使用位掩码时,您可以使用一个按位来测试多个属性。例如,如果您只关心玩家是跑步还是跳跃,您可以执行以下操作:
(PLAYER_RUNNING | PLAYER_JUMPING)
请注意,几乎每个编译器都会将{{1}}转换为单个常量,因此这会减少操作数,因为只检查了一个结构成员而不是两个。
答案 1 :(得分:1)
根据另一个优秀的答案,还有一个领域需要补充。的掩模即可。什么是面具?这听起来令人印象深刻,那又是什么?好吧,它只是一个数字 ......重要的部分 - 是数字所代表的。通常,当您想到一个掩码,一个数字时,您正在考虑掩码告诉您组成该数字的各个位的状态。
考虑一个unsigned integer
,它是4-bytes
的信息(通常,x86机器上通常是4个字节)。所有人都熟悉用于隔离unsigned int的低字节的公共掩码0xff
。举个例子:
ui = 73289 (00000000-00000001-00011110-01001001)
byte0 = ui & 0xff; // 0xff = binary 11111111 (255)
byte0 : 73 (01001001)
如上所述,mask
或0xff
只不过是255
的数字11111111
恰好具有ui
的二进制表示形式,可以确保 anded数字为ui
的将提供0xff
的第一个 8位(或字节)。
如果您感兴趣的掩码经常更改或者没有像bitmask
这样的可读十六进制等效值,那么简单地将表示感兴趣的位的数字指定给变量是很有用的。然后将该变量称为#define PLAYER_JUMPING (1U << 0)
#define PLAYER_SWIMMING (1U << 1)
#define PLAYER_RUNNING (1U << 2)
#define PLAYER_DEAD (1U << 3)
。 (仅此而已,只是分配给一个恰好代表您感兴趣的位状态的变量的数字)。
有了这个,让我们再看一下 esm 给出的例子:
if ((player[x] & (PLAYER_RUNNING | PLAYER_JUMPING)) ==
(PLAYER_RUNNING | PLAYER_JUMPING))
printf ("player[x] is running & jumping");
要查找哪些玩家正在跑步和跳跃,我们需要执行以下操作:
PLAYER_RUNNING | PLAYER_JUMPING
这是一个混乱的类型和一个乱七八糟的阅读,但如果我们将bitmask
的结果分配给变量并使用该变量(我们的mask_rj = PLAYER_RUNNING | PLAYER_JUMPING; // its value is '5' or (00000101)
if ((player[x] | mask_rj) == mask_rj)
printf ("player[x] is running & jumping");
),它可以更容易管理测试。 E.g:
#include <stdio.h>
#include <limits.h> // for CHAR_BIT
#if defined(__LP64__) || defined(_LP64)
# define BUILD_64 1
#endif
#ifdef BUILD_64
# define BITS_PER_LONG 64
#else
# define BITS_PER_LONG 32
#endif
#define PLAYER_JUMPING (1U << 0)
#define PLAYER_SWIMMING (1U << 1)
#define PLAYER_RUNNING (1U << 2)
#define PLAYER_DEAD (1U << 3)
char *binpad (unsigned long n, size_t sz);
char *binsep (unsigned long n, size_t sz, unsigned char szs, char sep);
int main (void) {
unsigned char players[] = { 0b00001000, 0b00000010,
0b00000101, 0b00000100 };
unsigned char nplayers = sizeof players/sizeof *players;
unsigned char mask_rj = PLAYER_RUNNING | PLAYER_JUMPING;
unsigned char i = 0;
printf ("\n mask_rj : %hhu (%s)\n\n",
mask_rj, binpad (mask_rj, sizeof mask_rj * CHAR_BIT));
for (i = 0; i < nplayers; i++)
printf (" player [%hhu] : %hhu (%s)\n",
i, players[i],
binpad (players[i], sizeof players[i] * CHAR_BIT));
for (i = 0; i < nplayers; i++)
if ((players[i] & mask_rj) == mask_rj)
printf ("\n player [%hhu] is Running & Jumping\n", i);
return 0;
}
char *binpad (unsigned long n, size_t sz)
{
static char s[BITS_PER_LONG + 1] = {0};
char *p = s + BITS_PER_LONG;
register size_t i;
for (i = 0; i < sz; i++)
*(--p) = (n>>i & 1) ? '1' : '0';
return p;
}
使用面具查找正在跑步和跳跃的玩家的简短示例将是:
$ ./bin/bitmask
mask_rj : 5 (00000101)
player [0] : 8 (00001000)
player [1] : 2 (00000010)
player [2] : 5 (00000101)
player [3] : 4 (00000100)
player [2] is Running & Jumping
<强>输出强>
<script >
//For calendar
$(".datepicker").datetimepicker({
inline: true,
showWeek: true,
firstDay: 1,
showDay: true,
timeformate: 'H:I',
});