使用按位或运算符 (|
) 来设置一个位。
number |= 1UL << n;
将会设置number
的第n
位。如果您想设置第1位,n
应该为零,一直到第n
位,n-1
。
如果number
比unsigned long
更宽,请使用1ULL
;在评估1UL << n
之前,不会提升1UL << n
,在这里,左移多于一个long
的宽度是未定义行为。 其他示例也适用。
使用按位与运算符(&
)来清除一位。
number &= ~(1UL << n);
这将清除 number
的第 n
位。您需要使用位NOT运算符 (~
) 反转比特串,然后进行AND运算。
XOR 运算符 (^
) 可以用于翻转一个比特位。
number ^= 1UL << n;
这将切换number
的第n
位。
虽然您没有要求,但我也可以添加一些内容。
要检查位,请将数字n
向右移动,然后进行按位与操作:
bit = (number >> n) & 1U;
这将把number
的第n
位的值放入变量bit
中。
在2进制补码C++实现中,以下代码可实现将第n
位设置为1
或0
:
number ^= (-x ^ number) & (1UL << n);
如果x
为1
,则会设置第n
位,如果x
为0
,则会清除该位。如果x
具有其他值,则会得到垃圾值。 x = !!x
将其转换为布尔值0或1。
为了使其独立于2的补码取反行为(在1的补码或符号/大小端C ++实现中,其中-1
的所有位都已设置,与之不同),请使用无符号取反。
number ^= (-(unsigned long)x ^ number) & (1UL << n);
或者unsigned long newbit = !!x; // Also booleanize to force 0 or 1
number ^= (-newbit ^ number) & (1UL << n);
通常情况下,在进行可移植位操作时使用无符号类型是一个好的选择。
或者
number = (number & ~(1UL << n)) | (x << n);
(number & ~(1UL << n))
会清除第n
位,并且(x << n)
会将第n
位设置为x
。一般来说最好不要直接复制/粘贴代码,所以许多人使用预处理器宏(如下面的社区wiki答案)或某种封装操作。
bit = (number >> x) & 1
? - aaronmannumber = number & ~(1 << n) | (x << n);
来将第n位改为x。 - leolystd::bitset<N>
。boost::dynamic_bitset
。#include <bitset>
#include <iostream>
int main()
{
std::bitset<5> x;
x[1] = 1;
x[2] = 0;
// Note x[0-4] valid
std::cout << x << std::endl;
}
./a.out
输出:
00010
另一种选择是使用位域:
struct bits {
unsigned int a:1;
unsigned int b:1;
unsigned int c:1;
};
struct bits mybits;
定义一个 3 位字段(实际上是三个 1 位字段)。现在,位操作变得更加简单:
设置或清除一个位:
mybits.b = 1;
mybits.c = 0;
切换一位:
mybits.a = !mybits.a;
mybits.b = ~mybits.b;
mybits.c ^= 1; /* all work */
检查一位:
if (mybits.c) //if mybits.c is non zero the next line below will execute
这仅适用于固定大小的位域。否则,您必须采用先前帖子中描述的位操作技术。
我使用在头文件中定义的宏来处理位的设置和清除:
/* a=target variable, b=bit number to act upon 0-n */
#define BIT_SET(a,b) ((a) |= (1ULL<<(b)))
#define BIT_CLEAR(a,b) ((a) &= ~(1ULL<<(b)))
#define BIT_FLIP(a,b) ((a) ^= (1ULL<<(b)))
#define BIT_CHECK(a,b) (!!((a) & (1ULL<<(b)))) // '!!' to make sure this returns 0 or 1
#define BITMASK_SET(x, mask) ((x) |= (mask))
#define BITMASK_CLEAR(x, mask) ((x) &= (~(mask)))
#define BITMASK_FLIP(x, mask) ((x) ^= (mask))
#define BITMASK_CHECK_ALL(x, mask) (!(~(x) & (mask)))
#define BITMASK_CHECK_ANY(x, mask) ((x) & (mask))
BITMASK_CHECK(x,y) ((x) & (y))
必须改为 ((x) & (y)) == (y)
,否则在多位掩码(例如 5
对比 3
)时会返回错误的结果。/向所有挖坟人问好 :)/ - brigadir1
应该被写成 (uintmax_t)1
或类似的形式,以防止任何人试图在 long
或更大类型上使用这些宏。 - M.MBITMASK_CHECK_ALL(x,y)
can be implemented as !~((~(y))|(x))
- Handy999!(~(x) & (y))
。 - Tavian Barnes有时候使用 enum
来给位(bits)命名是值得的:
enum ThingFlags = {
ThingMask = 0x0000,
ThingFlag0 = 1 << 0,
ThingFlag1 = 1 << 1,
ThingError = 1 << 8,
}
稍后使用名称。即写入
thingstate |= ThingFlag1;
thingstate &= ~ThingFlag0;
if (thing & ThingError) {...}
设置、清除和测试。这种方法可以将魔数从代码的其他部分隐藏起来。
除此之外,我推荐Paige Ruten的解决方案。
clearbits()
函数来代替 &= ~
。为什么要使用枚举?我认为枚举是用于创建一堆具有隐藏任意值的唯一变量,但你正在为每个变量分配明确的值。那么与将它们定义为变量相比,有什么好处呢? - endolithconst short
或其他方式相比唯一的优势是可以明确地将它们分组在一起。当你需要它们用于除位掩码之外的其他目的时,它们可以自动编号。当然,在C++中,它们也形成独立的类型,这为您提供了一些额外的静态错误检查。 - dmckee --- ex-moderator kittenThingError | ThingFlag1
,'enum ThingFlags' 的值是什么? - Luis Coloradoint
,由于对有符号类型进行隐式整数提升或按位运算可能会导致各种微妙的错误。例如,thingstate = ThingFlag1 >> 1
将调用实现定义的行为。 thingstate =(ThingFlag1 >> x)<< y
可能会调用未定义的行为。为了安全起见,请始终转换为无符号类型。 - Lundinenum My16Bits: unsigned short { ... };
- Aiken DrumThingMask
是零? - wizzwizz4/*
** Bit set, clear, and test operations
**
** public domain snippet by Bob Stout
*/
typedef enum {ERROR = -1, FALSE, TRUE} LOGICAL;
#define BOOL(x) (!(!(x)))
#define BitSet(arg,posn) ((arg) | (1L << (posn)))
#define BitClr(arg,posn) ((arg) & ~(1L << (posn)))
#define BitTst(arg,posn) BOOL((arg) & (1L << (posn)))
#define BitFlp(arg,posn) ((arg) ^ (1L << (posn)))
0000 0000 0000 0000 0000 0000 0000 0001 binary.
0000 0000 0000 0000 0000 0001 0000 0000 binary.
----[ TEST.C ]----------------------------------------------------------------
#define BOOL(x) (!(!(x)))
#define BitSet(arg,posn) ((arg) | (1L << (posn)))
#define BitClr(arg,posn) ((arg) & ~(1L << (posn)))
#define BitTst(arg,posn) BOOL((arg) & (1L << (posn)))
#define BitFlp(arg,posn) ((arg) ^ (1L << (posn)))
int bitmanip(int word)
{
word = BitSet(word, 2);
word = BitSet(word, 7);
word = BitClr(word, 3);
word = BitFlp(word, 9);
return word;
}
----[ TEST.OUT (disassembled) ]-----------------------------------------------
Module: C:\BINK\tst.c
Group: 'DGROUP' CONST,CONST2,_DATA,_BSS
Segment: _TEXT BYTE 00000008 bytes
0000 0c 84 bitmanip_ or al,84H ; set bits 2 and 7
0002 80 f4 02 xor ah,02H ; flip bit 9 of EAX (bit 1 of AH)
0005 24 f7 and al,0f7H
0007 c3 ret
No disassembly errors
----[ finis ]-----------------------------------------------------------------
arg
是 long long
,那么这会失败。1L
需要是最宽的类型,所以应该使用 (uintmax_t)1
。(你也可以试试 1ull
)。 - M.Mvalue is 0x55;
bitnum : 3rd.
使用&
运算符来检查位:
0101 0101
&
0000 1000
___________
0000 0000 (mean 0: False). It will work fine if the third bit is 1 (then the answer will be True)
0101 0101
^
0000 1000
___________
0101 1101 (Flip the third bit without affecting other bits)
| 运算符:设置位0101 0101
|
0000 1000
___________
0101 1101 (set the third bit without affecting other bits)
这是我最喜欢的位运算宏,适用于任何类型的无符号整数数组,从 unsigned char
到 size_t
(这是最大的类型,应该是高效工作的):
#define BITOP(a,b,op) \
((a)[(size_t)(b)/(8*sizeof *(a))] op ((size_t)1<<((size_t)(b)%(8*sizeof *(a)))))
设置一个位:
BITOP(array, bit, |=);
澄清一下:
BITOP(array, bit, &=~);
要切换一个比特位:
BITOP(array, bit, ^=);
为了测试一下:
if (BITOP(array, bit, &)) ...
等等。
BITOP(array, bit++, |=);
很可能不会达到调用者的预期效果。 - foraidtBITCELL(a,b) |= BITMASK(a,b);
(两者都使用 a
作为参数来确定大小,但后者永远不会评估 a
,因为它只出现在 sizeof
中)。 - R.. GitHub STOP HELPING ICE(size_t)
转换似乎只是为了确保使用%
进行一些无符号数学。可以用(unsigned)
代替。 - chux - Reinstate Monica(size_t)(b)/(8*sizeof *(a))
这个表达式在除法之前将 b
狭窄化了,这是不必要的。这只会在非常大的位数组中出现问题。但这依然是一个有趣的宏定义。 - chux - Reinstate Monica由于此处标记为“嵌入式”,我假设您正在使用微控制器。以上所有建议都是有效的并且可行(读取-修改-写入、联合、结构等)。
然而,在基于示波器的调试过程中,我惊奇地发现,与直接将值写入微控制器的PORTnSET/ PORTnCLEAR寄存器相比,这些方法在CPU周期方面有相当大的开销,这在有紧密循环/高频率ISR切换引脚的情况下会产生真正的影响。
对于不熟悉的人:在我的例子中,微控制器具有反映输出引脚状态的通用引脚状态寄存器PORTn,因此执行PORTn |= BIT_TO_SET会导致对该寄存器进行读取-修改-写入操作。但是,PORTnSET / PORTnCLEAR寄存器将“1”解释为“请使此位为1”(SET),将“0”解释为“请将此位清零”(CLEAR),将“0”解释为“无需更改引脚”。因此,您最终会得到两个端口地址,具体取决于您是设置还是清除该位(不总是方便的),但是会有一个更快的反应和较小的汇编代码。
先假设几件事情
num = 55
整数用于执行位运算(设置、获取、清除、切换)
n = 4
基于0的位位置,用于执行位运算。
bit = (num >> n) & 1;
它是如何工作的?
0011 0111 (55 in decimal)
>> 4 (right shift 4 times)
-----------------
0000 0011
& 0000 0001 (1 in decimal)
-----------------
=> 0000 0001 (final result)
n
次。 然后执行按位或 |
运算与 num
。num |= (1 << n); // Equivalent to; num = (1 << n) | num;
它是如何工作的?
0000 0001 (1 in decimal)
<< 4 (left shift 4 times)
-----------------
0001 0000
| 0011 0111 (55 in decimal)
-----------------
=> 0001 0000 (final result)
1 << n
。~ (1 << n)
。num
执行按位与&
操作。以上三个步骤可以写成 num & (~ (1 << n))
;num &= (~(1 << n)); // Equivalent to; num = num & (~(1 << n));
它是如何工作的?
0000 0001 (1 in decimal)
<< 4 (left shift 4 times)
-----------------
~ 0001 0000
-----------------
1110 1111
& 0011 0111 (55 in decimal)
-----------------
=> 0010 0111 (final result)
使用位异或^
运算符来切换一个比特位。位异或运算符的计算结果为1,当且仅当两个操作数的相应比特位不同时,否则结果为0。
这意味着要切换一个比特位,我们需要对要切换的比特位和数字1执行异或操作。
num ^= (1 << n); // Equivalent to; num = num ^ (1 << n);
它是如何工作的?
0 ^ 1 => 1
。1 ^ 1 => 0
。 0000 0001 (1 in decimal)
<< 4 (left shift 4 times)
-----------------
0001 0000
^ 0011 0111 (55 in decimal)
-----------------
=> 0010 0111 (final result)
推荐阅读 - C语言位运算练习及解答