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

您如何设置,清除和切换?

答案

设置一点

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

number |= 1UL << n;

这将设置numbern位。 n应该是零,如果你想设置1 ST 位,依此类推高达n-1如果你想设置的n个位。

如果number大于unsigned long则使用1ULL ; 直到对1UL << n进行评估后,才发生1UL << n提升,在这种情况下,未定义的行为是将偏移的长度大于long的宽度。其他所有示例也是如此。

清除一点

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

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

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

切换一点

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

number ^= 1UL << n;

这将切换n的个位number

检查一下

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

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

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

这将把数字的第n位的number放入可变bit

将第n位更改为x

在 2 的补码 C ++ 实现中,可以通过以下操作将第n位设置为10

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

如果x1 ,则将设置n位;如果x0 ,则将其清除。如果x具有其他值,则会产生垃圾。 x = !!x会将其布尔值设为 0 或 1。

为了使它独立于 2 的补码取反行为(其中-1设置了所有位,与 1 的补码或符号 / 幅度 C ++ 实现不同),请使用无符号取反。

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 进一步回答 )或某种封装形式。

使用标准 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 版本允许运行时大小的位组。

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

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

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

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

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

有时值得使用enum命名位:

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

然后使用这些名称 。即写

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

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

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

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

使用按位运算符: & |

设置000b最后一位:

foo = foo | 001b

要检查foo最后一位:

if ( foo & 001b ) ....

要清除foo最后一位:

foo = foo & 110b

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

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

例:

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)

这是我最喜欢的位算术宏,它适用于size_t unsigned charsize_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, &)) ...

等等

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

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

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