位运算符简介
本文简要概述了 C 风格的位运算符
引言
我注意到有些人似乎在位运算符方面遇到了问题,所以我决定写这篇简短的教程来介绍如何使用它们。
位简介
您可能会问,位是什么?
简单来说,位是我们用计算机做的一切的组成部分,即单独的0和1。您使用的所有数据都存储在计算机中,使用位。一个BYTE
由八个位组成,一个WORD
是两个BYTE
,或十六个位。而一个DWORD
是两个WORD
,或三十二个位。
0 1 0 0 0 1 1 1 1 0 0 0 0 1 1 1 0 1 1 1 0 1 0 0 0 1 1 1 1 0 0 0 || | | | || |+- bit 31 | | | bit 0 -+| | | | | | +-- BYTE 3 -----+--- BYTE 2 ----+--- BYTE 1 ----+-- BYTE 0 -----+ | | | +----------- WORD 1 ------------+----------- WORD 0 ------------+ | | +--------------------------- DWORD -----------------------------+
位运算符的美妙之处在于,您可以将BYTE
、WORD
或DWORD
用作小型数组或结构。使用位运算符,您可以检查或设置单个位甚至一组位的值。
十六进制数及其与位的关系
在处理位时,用二进制表示法(仅用0和1表示)来表示每个数字是相当困难的。为了解决这个问题,我们使用十六进制(基数为16)数。
您可能知道也可能不知道,需要四个位才能涵盖从零到十五的所有数字,这也恰好是单个十六进制数字的范围。这四位一组,或半个BYTE
,称为一个nibble。由于一个BYTE
中有两个nibble,我们可以使用两个十六进制数字来表示一个BYTE
的值。
NIBBLE HEX VALUE ====== ========= 0000 0 0001 1 0010 2 0011 3 0100 4 0101 5 0110 6 0111 7 1000 8 1001 9 1010 A 1011 B 1100 C 1101 D 1110 E 1111 F
因此,如果我们有一个BYTE
包含字母“r
”(ASCII码114),它将如下所示
0111 0010 binary 7 2 hexadecimal
我们可以将其写为“0x72
”
位运算符
有六个位运算符。它们是
- & AND 运算符
- | OR 运算符
- ^ XOR 运算符
- ~ 按位取反(或求反)运算符
- >> 右移运算符
- << 左移运算符。
与 (&) 运算符
与 (&) 运算符比较两个值,当且仅当被比较的两个值都具有相应的位设置时,它才返回一个具有设置位的计算结果。位使用以下表进行比较
1 & 1 == 1 1 & 0 == 0 0 & 1 == 0 0 & 0 == 0
一个理想的用途是设置一个掩码来检查特定位的值。假设我们有一个BYTE
包含一些位标志,我们想检查位四是否被设置。
BYTE b = 50; if ( b & 0x10 ) cout << "Bit four is set" << endl; else cout << "Bit four is clear" << endl;
这将产生以下计算
00110010 - b & 00010000 - & 0x10 ---------- 00010000 - result
因此,我们看到位四被设置了。
或 (|) 运算符
或 (|) 运算符比较两个值,如果其中一个值或另一个值,或两者都具有相应的位设置,则它返回一个具有设置位的计算结果。位使用以下表进行比较
1 | 1 == 1 1 | 0 == 1 0 | 1 == 1 0 | 0 == 0
一个理想的用途是确保某些位被设置。假设我们想确保某个值的位三被设置
BYTE b = 50; BYTE c = b | 0x04; cout << "c = " << c << endl;
这将产生以下计算
00110010 - b | 00000100 - | 0x04 ---------- 00110110 - result
异或 (^) 运算符
异或 (^) 运算符比较两个值,如果其中一个值或另一个值具有相应的位设置,但不是两者都具有,则它返回一个具有设置位的计算结果。位使用以下表进行比较
1 ^ 1 == 0 1 ^ 0 == 1 0 ^ 1 == 1 0 ^ 0 == 0
一个理想的用途是切换特定位。假设我们想切换位三和位四
BYTE b = 50; cout << "b = " << b << endl; b = b ^ 0x18; cout << "b = " << b << endl; b = b ^ 0x18; cout << "b = " << b << endl;
这将产生以下计算
00110010 - b ^ 00011000 - ^ 0x18 ---------- 00101010 - result 00101010 - b ^ 00011000 - ^ 0x18 ---------- 00110010 - result
按位取反 (~) 运算符
按位取反 (~) 运算符(或求反)运算符仅作用于一个值并将其反转,将所有1转换为0,所有0转换为1。一个理想的用途是将某些字节设置为0,并确保所有其他字节都设置为1,无论数据大小如何。假设我们想将所有位设置为1,除了位零和位一
BYTE b = ~0x03; cout << "b = " << b << endl; WORD w = ~0x03; cout << "w = " << w << endl;
这将产生以下计算
00000011 - 0x03 11111100 - ~0x03 b 0000000000000011 - 0x03 1111111111111100 - ~0x03 w
另一个理想的用途是将其与与 (&) 运算符结合使用,以确保某些位设置为0。假设我们想清除位四
BYTE b = 50; cout << "b = " << b << endl; BYTE c = b & ~0x10; cout << "c = " << c << endl;
这将产生以下计算
00110010 - b & 11101111 - ~0x10 ---------- 00100010 - result
右移 (>>) 和左移 (<<) 运算符
右移 (>>) 和左移 (<<) 运算符将位数移动指定的位数。右移 (>>) 运算符将位从高位移向低位。左移 (<<) 运算符将位从低位移向高位。这些运算符的一个用途是出于任何原因对齐位(请查看 MAKEWPARAM
、HIWORD
和 LOWORD
宏)
BYTE b = 12; cout << "b = " << b << endl; BYTE c = b << 2; cout << "c = " << c << endl; c = b >> 2; cout << "c = " << c << endl;
这将产生以下计算
00001100 - b 00110000 - b << 2 00000011 - b >> 2
位字段
使用位还可以做的另一件有趣的事情是使用位字段。通过位字段,您可以在BYTE
、WORD
或DWORD
内设置微型结构。例如,假设我们想跟踪日期,但我们想尽量节省内存。我们可以这样声明我们的结构
struct date_struct { BYTE day : 5, // 1 to 31 month : 4, // 1 to 12 year : 14; // 0 to 9999 } date;
在此示例中,day
字段占用最低的5位,month
占用接下来的4位,year
占用接下来的14位。因此,我们可以将日期结构存储在二十三位中,这包含在三个BYTE
中。第二十四位被忽略。如果我为每个字段使用整数声明,该结构将占用12个BYTE
。
|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0| | | | | +------ year ---------------+ month +-- day --+
现在让我们分解这个声明,看看我们正在做什么。
首先,我们将查看用于位字段结构的. 在这种情况下,我们使用了一个BYTE
。一个BYTE
是8位,通过使用它,编译器将分配一个BYTE
用于存储。但是,如果我们使用的结构超过8位,编译器将分配另一个BYTE
,即容纳我们结构的任意数量的BYTE
。如果我们使用了WORD
或DWORD
,编译器将分配总共32位来容纳我们的结构。
现在让我们看看各种字段是如何声明的。首先,我们有变量(day
、month
和year
),后面跟着一个冒号,将变量与其包含的位数分开。每个位字段用逗号分隔,列表以分号结束。
现在我们来看struct
声明。我们将位字段放入struct
中,这样我们就可以使用约定俗成的结构访问符号来访问结构成员。此外,由于我们无法获取位字段的地址,现在我们可以使用结构的地址。
date.day = 12; dateptr = &date; dateptr->year = 1852;