如何设置,清除和切换单个位?


Answers:


3594

设置一点

使用按位或运算符(|)进行设置。

number |= 1UL << n;

这将设置的n第一位numbern如果要设置1st位,则应为零;如果n-1要设置nth位,则应为upto 。

使用1ULL如果number超过更宽unsigned long; 1UL << n直到评估1UL << n了未定义行为的偏移量大于宽度之后,才会提升long。其他所有示例也是如此。

清除一点

使用按位AND运算符(&)清除一位。

number &= ~(1UL << n);

这将清除的n第一个位number。您必须使用按位NOT运算符(~)反转位字符串,然后将其取反。

切换一点

XOR运算符(^)可用于切换一位。

number ^= 1UL << n;

这将切换n的个位number

检查一下

您没有要求这样做,但我也可以添加它。

要检查一点,请将数字n右移,然后按位与它相乘:

bit = (number >> n) & 1U;

这会将n第th位的值number放入变量bit

将第n位更改为x

在2的补码C ++实现中,可以通过以下方式将nth位设置为10

number ^= (-x ^ number) & (1UL << n);

n,如果将被设置x1,如果清除x0。如果x还有其他价值,您将得到垃圾。 x = !!x会将其布尔值设为0或1。

为了使它独立于2的补码取反行为(-1与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))将清除nth位并将th位(x << n)设置nx

通常也不要复制/粘贴代码,这也是一个好主意,因此许多人使用预处理器宏(例如社区Wiki进一步回答)或某种封装。


127
我想指出的是,在具有本机支持位设置/清除(例如AVR微控制器)的平台上,每当x为一个常量,例如:(1 << 5)或const unsigned x =
Aaron

52
位=数字&(1 << x); 除非bit的类型为_Bool(<stdbool.h>),否则不会将bit x的值放入bit中。否则,bit = !!(数字&(1 << x)); 会..
克里斯·杨

23
您为什么不将最后一个更改为bit = (number >> x) & 1
aaronman

42
1是一个带int符号的文字。因此,此处的所有操作都对带符号的数字进行操作,而该数字并未由标准明确定义。标准不保证二进制补码或算术移位,因此最好使用1U
思源仁

50
我更喜欢number = number & ~(1 << n) | (x << n);将第n位更改为x。
jiasli

459

使用标准C ++库: std::bitset<N>

Boost版本: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;
}

[Alpha:] > ./a.out
00010

标准库编译时大小的位组相比,Boost版本允许运行时大小的位组。


34
+1。并不是说std​​ :: bitset可以在“ C”中使用,而是当作者用AFAIK“ C ++”标记他/她的问题时,您的答案是这里最好的... std :: vector <bool>是另一种方式,如果有人知道它的优缺点
paercebal

23
@andrewdotnich :(不幸的是)vector <bool>是将值存储为位的专业化。有关更多信息,请参见gotw.ca/publications/mill09.htm ...
Niklas

71
也许没人提到它,因为它被标记为嵌入式。在大多数嵌入式系统中,避免使用STL之类的瘟疫。在大多数嵌入式编译器中,增强支持可能是非常难得的选择。
伦丁

17
@马丁这是真的。除了诸如STL和模板之类的特定性能杀手之外,许多嵌入式系统甚至完全避免使用整个标准库,因为它们很难验证。大多数嵌入式分支都在拥抱像MISRA这样的标准,该标准需要静态代码分析工具(所有软件专业人员都应该使用这样的工具,而不仅仅是嵌入式专家)。通常,与通过整个标准库进行静态分析相比,人们有更好的事情要做-如果其源代码甚至可以在特定的编译器上使用。
伦丁

37
@Lundin:您的发言过于广泛(因此无济于事)。我确信我能找到真实的情况。这不会改变我的初衷。这两个类都非常适合在嵌入式系统中使用(并且我知道它们已被使用)。关于未在嵌入式系统上使用STL / Boost的最初观点也是错误的。我确信有些系统不使用它们,即使确实使用它们的系统也被明智地使用了,但是说它们没有被使用是不正确的(因为有某些系统在使用它们)。
马丁·约克

248

另一种选择是使用位字段:

struct bits {
    unsigned int a:1;
    unsigned int b:1;
    unsigned int c:1;
};

struct bits mybits;

定义一个3位字段(实际上是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

这仅适用于固定大小的位字段。否则,您必须诉诸以前的帖子中介绍的位旋转技术。


68
我一直发现使用位域是个坏主意。您无法控制位的分配顺序(从顶部到底部),这使得一次只能进行一次稳定的,便携式的值序列化。将DIY位算术与位域混在一起也是不可能的,例如制作一个可以同时测试几个位的掩码。您当然可以使用&&,并希望编译器可以对其进行优化...
R .. GitHub停止帮助ICE 2010年

34
位域在很多方面都是不好的,我几乎可以写一本书。实际上,对于需要符合MISRA-C要求的位现场程序,我几乎必须这样做。MISRA-C强制记录了所有实现定义的行为,因此我最后写了一篇有关位字段中可能出错的内容的文章。位顺序,字节序,填充位,填充字节,各种其他对齐问题,与位字段之间的隐式和显式类型转换,未使用int的UB等。相反,请使用按位运算符来减少错误和可移植代码。位字段是完全冗余的。
伦丁

44
像大多数语言功能一样,位字段可以正确使用,也可以被滥用。如果您需要将多个小值打包到单个int中,则位字段可能非常有用。另一方面,如果您开始假设位字段如何映射到实际包含int的对象,那么您只是在自找麻烦。
Ferruccio

4
@endolith:那不是一个好主意。您可以使其工作,但是不一定可以移植到其他处理器,其他编译器甚至同一编译器的下一发行版中。
Ferruccio 2012年

3
@Yasky和Ferruccio对于这种方法对sizeof()的使用得到了不同的答案,这不仅说明了编译器之间的兼容性问题,而且还说明了硬件之间的兼容性问题。有时我们会自欺欺人,已经用语言或定义的运行时解决了这些问题,但实际上归结为“它将在我的机器上工作吗?”。你们这些内心的家伙对我表示敬意(和同情)。
凯利·法国

181

我使用在头文件中定义的宏来处理位设置和清除:

/* 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

/* x=target variable, y=mask */
#define BITMASK_SET(x,y) ((x) |= (y))
#define BITMASK_CLEAR(x,y) ((x) &= (~(y)))
#define BITMASK_FLIP(x,y) ((x) ^= (y))
#define BITMASK_CHECK_ALL(x,y) (((x) & (y)) == (y))   // warning: evaluates y twice
#define BITMASK_CHECK_ANY(x,y) ((x) & (y))

17
嗯,我意识到这是5年的历史了,但这些宏中都没有重复论点,Dan
Robert Kelly

11
BITMASK_CHECK(x,y) ((x) & (y))((x) & (y)) == (y)否则必须返回,否则它会在多位掩码(例如5vs 。3)/ *向所有掘墓者返回错误的结果:)* /
brigadir 2014年

7
1(uintmax_t)1如果有人尝试在long更大或更大型的类型上使用这些宏,则应为或相似
MM

2
BITMASK_CHECK_ALL(x,y)可以实现为!~((~(y))|(x))
Handy999 '18

3
@ Handy999在应用德摩根定律并重新安排获得法律后,为什么这样做!(~(x) & (y))
行之有效

114

它有时是值得使用enum命名的位:

enum ThingFlags = {
  ThingMask  = 0x0000,
  ThingFlag0 = 1 << 0,
  ThingFlag1 = 1 << 1,
  ThingError = 1 << 8,
}

然后使用这些名称。即写

thingstate |= ThingFlag1;
thingstate &= ~ThingFlag0;
if (thing & ThingError) {...}

进行设置,清除和测试。这样,您就可以在其余代码中隐藏幻数。

除此之外,我赞同杰里米的解决方案。


1
或者,您可以使用clearbits()函数代替&= ~。您为什么为此使用枚举?我以为是用隐藏的任意值创建一堆唯一变量,但是您要为每个变量分配一个确定值。那么与仅将它们定义为变量相比有什么好处?
endlith 2011年

4
@endolith:enum在c编程中,将s用于相关常量集可以追溯很长时间。我怀疑对于现代编译器而言,唯一的优势const short是将它们明确地组合在一起。当您希望它们用于位掩码之外的其他东西时,您会得到自动编号。当然,在c ++中,它们还形成不同的类型,这为您提供了一些额外的静态错误检查。
dmckee ---前主持人小猫,

如果没有为每个可能的位值定义常量,则将进入未定义的枚举常量。例如,的enum ThingFlags值是ThingError|ThingFlag1多少?
2014年

6
如果使用此方法,请记住枚举常量始终为带符号类型int。由于隐式整数提升或对有符号类型进行按位运算,这可能导致各种形式的细微错误。thingstate = ThingFlag1 >> 1例如将调用实现定义的行为。thingstate = (ThingFlag1 >> x) << y可以调用未定义的行为。等等。为了安全起见,请始终强制转换为无符号类型。
伦丁2015年

1
@Lundin:从C ++ 11开始,您可以设置枚举的基础类型,例如: enum My16Bits: unsigned short { ... };
Aiken Drum

47

snip-c.zip的bitops.h:

/*
**  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)))

好,让我们分析一下...

您似乎在所有这些方面都遇到问题的常见表达方式是“(1L <<(posn))”。所有这些操作就是创建一个只有一个位的掩码,该掩码将适用于任何整数类型。“ posn”参数指定您想要该位的位置。如果posn == 0,则此表达式的计算结果为:

0000 0000 0000 0000 0000 0000 0000 0001 binary.

如果posn == 8,它将计算为:

0000 0000 0000 0000 0000 0001 0000 0000 binary.

换句话说,它仅创建一个0字段,在指定位置带有1。唯一棘手的部分是在BitClr()宏中,我们需要在1的字段中设置单个0位。这可以通过使用与代字号(〜)运算符相同的表达式的1的补码来实现。

一旦创建了掩码,就可以使用按位运算符(&)或(|)和xor(^)运算符,按照您的建议将其应用于自变量。由于掩码的类型为long,因此宏在char,short,int或long上也可以使用。

最重要的是,这是对整个问题类别的通用解决方案。当然,每次您需要时,都可以使用显式掩码值重写这些宏的等效项,但是为什么这样做呢?请记住,宏替换发生在预处理器中,因此生成的代码将反映以下事实:编译器将值视为常量。也就是说,使用通用宏与每次需要“重新发明轮子”一样有效。做位操作。

不服气?这是一些测试代码-我使用Watcom C进行了全面优化,而没有使用_cdecl,因此所产生的反汇编应尽可能干净:

---- [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(已分解)] -------------------------------------- ---------

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] ------------------------------------------- ----------------------


3
关于此的两件事:(1)在仔细阅读宏时,有些人可能会错误地认为宏实际上已在arg中设置/清除/翻转了位,但是没有分配;(2)您的test.c不完整;我怀疑如果您遇到更多案例,会发现问题(阅读练习)
Dan Dan

19
-1这只是奇怪的混淆。永远不要通过在宏后面隐藏语言语法来重新发明C语言,这是非常糟糕的做法。然后是一些奇怪的事情:首先,对1L进行签名,这意味着所有位操作都将在签名类型上执行。传递给这些宏的所有内容都将以带符号的形式返回。不好。其次,这将在较小的CPU上非常低效地工作,因为当操作本可以处于int级别时,它会强制执行很长时间。第三,类似函数的宏是万恶之源:您根本没有类型安全性。同样,先前关于无分配的评论也非常有效。
伦丁

2
如果arg为,则将失败long long1L必须是尽可能宽的类型,因此(uintmax_t)1。(您可能会逃脱1ull
MM

您是否针对代码大小进行了优化?在Intel主流CPU上,此函数返回后,在读取AX或EAX时会出现部分寄存器停顿的情况,因为它会写入EAX的8位组件。(在AMD CPU上,或其他没有将部分寄存器重命名为完整寄存器的其他CPU上也不错 。Haswell/ Skylake不会分别重命名AL,但会重命名AH。
彼得·科德斯

37

使用按位运算符: & |

要设置最后一位000b

foo = foo | 001b

要检查中的最后一位foo

if ( foo & 001b ) ....

要清除最后一点foo

foo = foo & 110b

XXXb为了清楚起见。您可能将要使用HEX表示形式,具体取决于要打包位的数据结构。


7
C中没有二进制表示法。二进制整数常量是非标准扩展。
伦丁2015年

使用XOR进行切换:foo = foo ^ MY_MASK
Peter L

使用NOT反转遮罩以进行清除:foo = foo & ~MY_MASK
Peter L

32

对于初学者,我想用一个例子来解释一下:

例:

value 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)

26

这是我最喜欢的位算术宏,它适用于从0 unsigned char到0的任何类型的无符号整数数组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, &)) ...

等等


5
读起来不错,但应该注意可能的副作用。BITOP(array, bit++, |=);在循环中使用将很可能不会执行呼叫者想要的操作。
foraidt

确实。=)您可能更喜欢的一种变体是将其分成2个宏,一个用于寻址数组元素,另一个用于将位移至位置ala BITCELL(a,b) |= BITMASK(a,b);(两者均a作为确定大小的参数,但后者从不求值,a因为它仅出现在中sizeof
R .. GitHub停止帮助ICE,2010年

1
@R ..这个答案确实很老,但是在这种情况下,我可能更喜欢函数而不是宏。
PC Luddite

辅修:第三(size_t)投似乎只存在,以确保一些无符号数%。可以(unsigned)在那里。
chux-恢复莫妮卡

(size_t)(b)/(8*sizeof *(a))不必要可以缩小b师前。只有非常大的位数组才有问题。仍然是一个有趣的宏。
chux-恢复莫妮卡

25

因为这被标记为“嵌入式”,所以我假设您正在使用微控制器。以上所有建议都是有效且可行的(读取,修改,写入,联合,结构等)。

但是,在基于示波器的调试中,我惊讶地发现与直接将值写入微控制器的PORTnSET / PORTnCLEAR寄存器相比,这些方法在CPU周期上具有相当大的开销,这在存在紧密循环/高电平的情况下确实有所不同频率ISR的切换引脚。

对于那些不熟悉的人:在我的示例中,微控制器具有反映输出引脚的通用引脚状态寄存器PORTn,因此执行PORTn | = BIT_TO_SET会导致对该寄存器进行读-修改-写操作。但是,PORTnSET / PORTnCLEAR寄存器为1表示“请将该位设为1”(SET)或“请将该位设为0”(CLEAR),为0则表示“请单独保留该引脚”。所以,你最终有两个端口地址取决于是否你设置或清除该位(并不总是很方便),但很多更快的反应和更小的汇编代码。


Micro是Coldfire MCF52259,在Codewarrior中使用C。查看反汇编程序/汇编程序是一个有用的练习,因为它显示了CPU甚至最基本的操作都必须经过的所有步骤。<br>我们还在时间紧迫的循环中发现了其他占用CPU的指令-通过执行var%= max_val来限制变量,每个时间都要花费大量CPU周期,而if(var> max_val)var- = max_val仅使用几个指示。<br>这里还有一些其他技巧的很好指南:codeproject.com/Articles/6154/…–
John U

更为重要的是,辅助内存映射的I / O寄存器提供了原子更新的机制。如果序列被中断,读/修改/写可能会变得非常糟糕。
Ben Voigt

1
请记住,所有端口寄存器都将定义为volatile,因此编译器无法对涉及此类寄存器的代码执行任何优化。因此,优良作法是反汇编此类代码,并在汇编程序级别查看其结果。
伦丁2015年

24

位域方法在嵌入式领域具有其他优势。您可以定义直接映射到特定硬件寄存器中位的结构。

struct HwRegister {
    unsigned int errorFlag:1;  // one-bit flag field
    unsigned int Mode:3;       // three-bit mode field
    unsigned int StatusCode:4;  // four-bit status code
};

struct HwRegister CR3342_AReg;

您需要了解位打包顺序-我认为它首先是MSB,但这可能取决于实现。另外,请验证编译器如何处理跨越字节边界的字段。

然后,您可以像以前一样读取,写入,测试各个值。


2
关于位域的几乎所有内容都是实现定义的。即使您设法找到有关特定编译器如何实现它们的所有详细信息,在代码中使用它们肯定也将使其不可移植。
伦丁

1
@Lundin-的确如此,但是嵌入式系统的摆弄(尤其是在硬件寄存器中,这是我的回答所涉及的)永远不会有用地移植。
罗迪

1
也许不在完全不同的CPU之间。但是您很可能希望它在编译器之间和不同项目之间具有可移植性。而且有很多根本与硬件无关的嵌入式“位纠缠”,例如数据协议编码/解码。
伦丁

...并且如果您习惯于使用位字段进行嵌入式编程,那么您会发现X86代码运行得更快,更精简。不是在让整个机器粉碎基准的简单基准中,而是在程序争夺资源的现实世界中的多任务环境中。优势CISC-其最初的设计目标是比总线和慢速存储器更快地弥补CPU。

20

在任意类型的变量的任意位置检查一点:

#define bit_test(x, y)  ( ( ((const char*)&(x))[(y)>>3] & 0x80 >> ((y)&0x07)) >> (7-((y)&0x07) ) )

用法示例:

int main(void)
{
    unsigned char arr[8] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF };

    for (int ix = 0; ix < 64; ++ix)
        printf("bit %d is %d\n", ix, bit_test(arr, ix));

    return 0;
}

注意: 这样做是为了快速(赋予其灵活性)和不分枝。编译Sun Studio 8时,它可以产生有效的SPARC机器代码。我还在amd64上使用MSVC ++ 2008测试了它。可以制作相似的宏来设置和清除位。与许多其他解决方案相比,此解决方案的主要区别在于,它几乎可以在任何类型的变量中的任何位置使用。


20

更一般的,对于任意大小的位图:

#define BITS 8
#define BIT_SET(  p, n) (p[(n)/BITS] |=  (0x80>>((n)%BITS)))
#define BIT_CLEAR(p, n) (p[(n)/BITS] &= ~(0x80>>((n)%BITS)))
#define BIT_ISSET(p, n) (p[(n)/BITS] &   (0x80>>((n)%BITS)))

2
CHAR_BIT已经由定义limits.h,您不需要自己放置BITS(实际上,这样做会使代码更糟)
MM

14

该程序将任何数据位从0更改为1或从1更改为0:

{
    unsigned int data = 0x000000F0;
    int bitpos = 4;
    int bitvalue = 1;
    unsigned int bit = data;
    bit = (bit>>bitpos)&0x00000001;
    int invbitvalue = 0x00000001&(~bitvalue);
    printf("%x\n",bit);

    if (bitvalue == 0)
    {
        if (bit == 0)
            printf("%x\n", data);
        else
        {
             data = (data^(invbitvalue<<bitpos));
             printf("%x\n", data);
        }
    }
    else
    {
        if (bit == 1)
            printf("elseif %x\n", data);
        else
        {
            data = (data|(bitvalue<<bitpos));
            printf("else %x\n", data);
        }
    }
}

14

如果您要花很多时间在摆弄,您可能需要使用遮罩,这样可以使整个过程更快。以下功能非常快速且仍然很灵活(它们允许在任何大小的位图中旋转位)。

const unsigned char TQuickByteMask[8] =
{
   0x01, 0x02, 0x04, 0x08,
   0x10, 0x20, 0x40, 0x80,
};


/** Set bit in any sized bit mask.
 *
 * @return    none
 *
 * @param     bit    - Bit number.
 * @param     bitmap - Pointer to bitmap.
 */
void TSetBit( short bit, unsigned char *bitmap)
{
    short n, x;

    x = bit / 8;        // Index to byte.
    n = bit % 8;        // Specific bit in byte.

    bitmap[x] |= TQuickByteMask[n];        // Set bit.
}


/** Reset bit in any sized mask.
 *
 * @return  None
 *
 * @param   bit    - Bit number.
 * @param   bitmap - Pointer to bitmap.
 */
void TResetBit( short bit, unsigned char *bitmap)
{
    short n, x;

    x = bit / 8;        // Index to byte.
    n = bit % 8;        // Specific bit in byte.

    bitmap[x] &= (~TQuickByteMask[n]);    // Reset bit.
}


/** Toggle bit in any sized bit mask.
 *
 * @return   none
 *
 * @param   bit    - Bit number.
 * @param   bitmap - Pointer to bitmap.
 */
void TToggleBit( short bit, unsigned char *bitmap)
{
    short n, x;

    x = bit / 8;        // Index to byte.
    n = bit % 8;        // Specific bit in byte.

    bitmap[x] ^= TQuickByteMask[n];        // Toggle bit.
}


/** Checks specified bit.
 *
 * @return  1 if bit set else 0.
 *
 * @param   bit    - Bit number.
 * @param   bitmap - Pointer to bitmap.
 */
short TIsBitSet( short bit, const unsigned char *bitmap)
{
    short n, x;

    x = bit / 8;    // Index to byte.
    n = bit % 8;    // Specific bit in byte.

    // Test bit (logigal AND).
    if (bitmap[x] & TQuickByteMask[n])
        return 1;

    return 0;
}


/** Checks specified bit.
 *
 * @return  1 if bit reset else 0.
 *
 * @param   bit    - Bit number.
 * @param   bitmap - Pointer to bitmap.
 */
short TIsBitReset( short bit, const unsigned char *bitmap)
{
    return TIsBitSet(bit, bitmap) ^ 1;
}


/** Count number of bits set in a bitmap.
 *
 * @return   Number of bits set.
 *
 * @param    bitmap - Pointer to bitmap.
 * @param    size   - Bitmap size (in bits).
 *
 * @note    Not very efficient in terms of execution speed. If you are doing
 *        some computationally intense stuff you may need a more complex
 *        implementation which would be faster (especially for big bitmaps).
 *        See (http://graphics.stanford.edu/~seander/bithacks.html).
 */
int TCountBits( const unsigned char *bitmap, int size)
{
    int i, count = 0;

    for (i=0; i<size; i++)
        if (TIsBitSet(i, bitmap))
            count++;

    return count;
}

请注意,要以16位整数设置位'n',请执行以下操作:

TSetBit( n, &my_int);

您需要确保位数在您传递的位图范围内。请注意,对于字节,字,双字,qword等在内存中正确相互映射的小端处理器,主要原因是小端处理器比大端处理器“更好”,是啊,我觉得火焰之战正在到来上...)。


2
不要将表用于可以由单个运算符实现的功能。TQuickByteMask [n]等于(1 << n)。另外,短说是一个很糟糕的主意。/和%实际上将是一个除法,而不是按位移位/按位,并且因为不能以2的幂进行有符号除法。您应该使参数类型为unsigned int!
R .. GitHub停止帮助ICE,2010年

这有什么意义呢?它只会使代码更慢,更难阅读吗?我看不到任何优势。C语言程序员更容易阅读1u << n,并且希望可以将其翻译成单个时钟滴答CPU指令。另一方面,您的划分将转换为大约10个滴答声,或者甚至高达100个滴答声,具体取决于特定体系结构对划分的处理程度。至于位图功能,拥有一个将每个位索引转换为字节索引的查找表来优化速度会更有意义。
伦丁

2
对于大/小端,大端将以相同的方式映射整数和原始数据(例如字符串):在整个位图中,从左到右的msb到lsb。尽管little endian会将整数从左到右映射为7-0、15-8、23-18、31-24,但是原始数据仍然是从左到右的msb到lsb。因此,对于您的特定算法而言,多少字节序更好是完全不对的,这似乎是相反的。
伦丁

2
@R ..如果您的平台无法像旧的微芯片MCU一样有效地移动,则表可能会很有用,但是当然,样本中的划分绝对是效率低下的
jeb

12

用这个:

int ToggleNthBit ( unsigned char n, int num )
{
    if(num & (1 << n))
        num &= ~(1 << n);
    else
        num |= (1 << n);

    return num;
}

5
好吧,它使用低效的分支。
asdf

3
@asdf编译器的工作是输出最有效的二进制文件,程序员的工作是编写清晰的代码
MM

3
这是测试,设置和清除特定位的良好演示。但是,这是一个非常糟糕的切换方式。
Ben Voigt

10

扩大bitset答案:

#include <iostream>
#include <bitset>
#include <string>

using namespace std;
int main() {
  bitset<8> byte(std::string("10010011");

  // Set Bit
  byte.set(3); // 10010111

  // Clear Bit
  byte.reset(2); // 10010101

  // Toggle Bit
  byte.flip(7); // 00010101

  cout << byte << endl;

  return 0;
}

10

如果要在Linux内核中使用C编程执行所有这些操作,则建议使用Linux内核的标准API。

参见https://www.kernel.org/doc/htmldocs/kernel-api/ch02s03.html

set_bit  Atomically set a bit in memory
clear_bit  Clears a bit in memory
change_bit  Toggle a bit in memory
test_and_set_bit  Set a bit and return its old value
test_and_clear_bit  Clear a bit and return its old value
test_and_change_bit  Change a bit and return its old value
test_bit  Determine whether a bit is set

注意:此处整个操作只需一步即可完成。因此,即使在SMP计算机上,也保证了所有这些原子性,并且对于保持处理器之间的一致性很有用。


9

Visual C 2010,也许还有许多其他编译器,都直接支持内置的布尔操作。一个位具有两个可能的值,就像布尔值一样,因此我们可以改用布尔值-即使它们占用的空间大于单个位。以这种形式记忆。这可以正常工作,即使sizeof()操作员也可以正常工作。

bool    IsGph[256], IsNotGph[256];

//  Initialize boolean array to detect printable characters
for(i=0; i<sizeof(IsGph); i++)  {
    IsGph[i] = isgraph((unsigned char)i);
}

因此,对于您的问题,IsGph[i] =1IsGph[i] =0使设置和清除布尔值变得容易。

查找无法打印的字符:

//  Initialize boolean array to detect UN-printable characters, 
//  then call function to toggle required bits true, while initializing a 2nd
//  boolean array as the complement of the 1st.
for(i=0; i<sizeof(IsGph); i++)  {
    if(IsGph[i])    {
         IsNotGph[i] = 0;
    }   else   {
         IsNotGph[i] = 1;
    }
}

请注意,此代码没有什么“特殊的”。它对待就像一个整数-从技术上来说,它是。一个1位整数,可以容纳2个值,并且只能包含2个值。

我曾经使用这种方法来查找重复的贷方记录,其中贷方编号是ISAM密钥,使用6位数的贷方编号作为位数组的索引。经过8个月的迅速开发,证明了我们从中获取数据的大型机系统实际上发生了故障。位数组的简单性使其对正确性的置信度很高-例如,与搜索方法相比。


大多数编译器确实将std :: bitset实现为位
galinette 2014年

@galinette,同意。在这方面,头文件#include <bitset>是一个很好的资源。另外,特殊类vector <bool>用于需要更改矢量大小的情况。C ++ STL,第二版,Nicolai M. Josuttis分别在第650页和第281页上详尽地介绍了它们。C ++ 11为std :: bitset添加了一些新功能,我特别感兴趣的是无序容器中的哈希函数。感谢您的注意!我将删除我的抽筋评论。在网络上已经有足够的垃圾了。我不想添加到它。

3
每个至少使用一个完整的存储字节bool。甚至4个字节用于C89的设置使用int来实现bool
MM

@MattMcNabb,您是对的。在C ++中,标准未指定实现布尔值所需的int类型的大小。我意识到这个答案在前一段时间是错误的,但是决定将其留在这里,因为人们显然发现它很有用。对于那些想要使用位的人galinette的注释和我的位库在这里都是最有用的... stackoverflow.com/a/16534995/1899861

2
@RocketRoy:可能值得更改声称这是“位操作”示例的句子。
Ben Voigt

6

使用此处定义的运算符之一。

要设置位,请使用int x = x | 0x?;,其中?位是二进制形式的位置。


2
0x是十六进制文字而不是二进制文字的前缀。
Ben Voigt

5

这是我使用的一些宏:

SET_FLAG(Status, Flag)            ((Status) |= (Flag))
CLEAR_FLAG(Status, Flag)          ((Status) &= ~(Flag))
INVALID_FLAGS(ulFlags, ulAllowed) ((ulFlags) & ~(ulAllowed))
TEST_FLAGS(t,ulMask, ulBit)       (((t)&(ulMask)) == (ulBit))
IS_FLAG_SET(t,ulMask)             TEST_FLAGS(t,ulMask,ulMask)
IS_FLAG_CLEAR(t,ulMask)           TEST_FLAGS(t,ulMask,0)

5

使用变量

int value, pos;

价值-数据
位-我们想要设置,清除或切换的位的位置。

设置一点:

value = value | 1 << pos;

清除一点:

value = value & ~(1 << pos); 

切换一下:

value = value ^ 1 << pos;

5
int set_nth_bit(int num, int n){    
    return (num | 1 << n);
}

int clear_nth_bit(int num, int n){    
    return (num & ~( 1 << n));
}

int toggle_nth_bit(int num, int n){    
    return num ^ (1 << n);
}

int check_nth_bit(int num, int n){    
    return num & (1 << n);
}

返回类型check_nth_bit可以为bool
Xeverous

@Xeverous是的,这取决于呼叫者的意图
Sazzad Hissain Khan

5

首先假设一些事情
num = 55整数执行位运算(设置,获取,清除,切换)。
n = 4基于0的位位置以执行按位运算。

如何获得一点?

  1. 为了得到nthNUM右移位numn倍。然后&与1 进行按位与运算。
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)

怎么设置?

  1. 设置数字的特定位。左移1 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. 左移1 n次,即1 << n
  2. 用上述结果执行按位补码。这样第n个比特就不会被置位,其余的比特会被置位~ (1 << n)
  3. 最后,&对上述结果和进行按位与运算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)

如何切换一下?

为了切换一点,我们使用按位XOR ^运算符。如果两个操作数的对应位不同,则按位XOR运算符的计算结果为1,否则为0。

这意味着要切换一个位,我们需要对要切换的位和1执行XOR操作。

num ^= (1 << n);    // Equivalent to; num = num ^ (1 << n);

怎么运行的?

  • 如果要切换的位为0,则0 ^ 1 => 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)

推荐阅读- 按位操作员练习


感谢您的详细解释。这是BIT魔术练习题的练习链接
Chandra Shekhar

4

如何设置,清除和切换单个位?

在尝试形成遮罩时要解决常见的编码陷阱:
1并不总是足够宽

如果number类型大于,会发生什么问题1
x对于1 << x导致不确定行为(UB)的转变而言,可能太大了。即使x不是太大,~也可能不会翻转足够的最高有效位。

// assume 32 bit int/unsigned
unsigned long long number = foo();

unsigned x = 40; 
number |= (1 << x);  // UB
number ^= (1 << x);  // UB
number &= ~(1 << x); // UB

x = 10;
number &= ~(1 << x); // Wrong mask, not wide enough

确保1足够宽:

代码可以使用,1ull也可以花哨地(uintmax_t)1使编译器进行优化。

number |= (1ull << x);
number |= ((uintmax_t)1 << x);

或强制转换-导致编码/审查/维护问题,使强制转换正确并保持最新状态。

number |= (type_of_number)1 << x;

1通过强制进行宽度最小的数学运算来轻轻地促进运算number

number |= (number*0 + 1) << x;

与大多数位操作,最好与工作无符号的类型,而不是签署


一个老问题有趣的样子!既不number |= (type_of_number)1 << x;也不number |= (number*0 + 1) << x;适合设置一个符号类型的符号位......作为事实上,也不是number |= (1ull << x);。有没有一种便携式的位置定位方法?
chqrlie

1
@chqrlie IMO,最好避免使用无符号类型,这是避免设置符号位并避免UB或IDB移位的最佳方法。高度可移植的移位签名代码太复杂,难以接受。
chux-恢复莫妮卡

3

一个C ++ 11模板版本(放入标头):

namespace bit {
    template <typename T1, typename T2> inline void set  (T1 &variable, T2 bit) {variable |=  ((T1)1 << bit);}
    template <typename T1, typename T2> inline void clear(T1 &variable, T2 bit) {variable &= ~((T1)1 << bit);}
    template <typename T1, typename T2> inline void flip (T1 &variable, T2 bit) {variable ^=  ((T1)1 << bit);}
    template <typename T1, typename T2> inline bool test (T1 &variable, T2 bit) {return variable & ((T1)1 << bit);}
}

namespace bitmask {
    template <typename T1, typename T2> inline void set  (T1 &variable, T2 bits) {variable |= bits;}
    template <typename T1, typename T2> inline void clear(T1 &variable, T2 bits) {variable &= ~bits;}
    template <typename T1, typename T2> inline void flip (T1 &variable, T2 bits) {variable ^= bits;}
    template <typename T1, typename T2> inline bool test_all(T1 &variable, T2 bits) {return ((variable & bits) == bits);}
    template <typename T1, typename T2> inline bool test_any(T1 &variable, T2 bits) {return variable & bits;}
}

该代码已损坏。(此外,为什么还要;定义函数?)
melpomene

@melpomene代码没有损坏,我确实对其进行了测试。您是说它不会编译还是结果错误?关于多余的“;” 我不记得了,那些确实可以删除。
约基姆·克里斯蒂安森

(variable & bits == bits)
melpomene '18

谢谢您的注意,应该是((variable & bits) == bits)
Joakim L. Christiansen

使用std::bitset在C ++ 11
pqnet

0

该程序基于@Jeremy的上述解决方案。如果有人希望快速玩转。

public class BitwiseOperations {

    public static void main(String args[]) {

        setABit(0, 4); // set the 4th bit, 0000 -> 1000 [8]
        clearABit(16, 5); // clear the 5th bit, 10000 -> 00000 [0]
        toggleABit(8, 4); // toggle the 4th bit, 1000 -> 0000 [0]
        checkABit(8,4); // check the 4th bit 1000 -> true 
    }

    public static void setABit(int input, int n) {
        input = input | ( 1 << n-1);
        System.out.println(input);
    }


    public static void clearABit(int input, int n) {
        input = input & ~(1 << n-1);
        System.out.println(input);
    }

    public static void toggleABit(int input, int n) {
        input = input ^ (1 << n-1);
        System.out.println(input);
    }

    public static void checkABit(int input, int n) {
        boolean isSet = ((input >> n-1) & 1) == 1; 
        System.out.println(isSet);
    }
}


Output :
8
0
0
true

-2

尝试使用C语言中的以下功能之一来更改n位:

char bitfield;

// Start at 0th position

void chang_n_bit(int n, int value)
{
    bitfield = (bitfield | (1 << n)) & (~( (1 << n) ^ (value << n) ));
}

要么

void chang_n_bit(int n, int value)
{
    bitfield = (bitfield | (1 << n)) & ((value << n) | ((~0) ^ (1 << n)));
}

要么

void chang_n_bit(int n, int value)
{
    if(value)
        bitfield |= 1 << n;
    else
        bitfield &= ~0 ^ (1 << n);
}

char get_n_bit(int n)
{
    return (bitfield & (1 << n)) ? 1 : 0;
}

value << n可能会导致不确定的行为
MM
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.