如何在 C/C++ 中设置、清除和切换位?

有帮助吗?

解决方案

设置一点

使用按位或运算符 (|) 设置一点。

number |= 1UL << n;

这将设置 n第一点 number. n 应该为零,如果你想设置 1st 位等等 n-1, ,如果你想设置 n第一点。

使用 1ULL 如果 numberunsigned long;促进 1UL << n 直到评估后才会发生 1UL << n 移动超过 a 的宽度是未定义的行为 long. 。这同样适用于所有其余示例。

清理一点

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

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

这将清除 n第一点 number. 。您必须使用按位 NOT 运算符 (~),然后与它。

稍微切换一下

异或运算符 (^) 可以用来切换一点。

number ^= 1UL << n;

这将切换 n第一点 number.

检查一下

你没有要求这个,但我不妨添加它。

要检查某个位,请将数字 n 向右移动,然后按位与:

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

这会将值 n第一点 number 到变量中 bit.

改变 n第 位到 X

设置 nth 位到任一 1 或者 0 可以通过以下 2 的补码 C++ 实现来实现:

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

少量 n 将被设置,如果 x1, ,并清除如果 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.

一般来说,不要复制/粘贴代码通常也是一个好主意,而且很多人使用预处理器宏(例如 社区维基的答案进一步向下)或某种封装。

其他提示

使用标准 C++ 库: std::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;
}

[Alpha:] > ./a.out
00010

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

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

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

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

然后使用 名字 稍后的。IE。写

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

设置、清除和测试。这样你就可以从代码的其余部分隐藏神奇的数字。

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

剪切-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位。这是通过使用与Tilde(〜)操作员表示的1个表达式的1补充来完成的。

一旦创建了掩码,就可以按照您的建议将其应用于参数,并通过使用(&)或(|)和XOR(^)运算符。由于面具的类型很长,因此宏对Char,Short's,Int或Long的宏同样有效。

最重要的是,这是解决整个问题的一般解决方案。当然,每次需要一个具有明确掩码值的宏的等效词,都可能甚至合适,但是为什么要这样做呢?请记住,宏替换发生在预处理器中,因此生成的代码将反映以下事实:编译器将值视为恒定-IE每当您需要进行一些操纵时,使用广义宏来“重新发明车轮”同样有效。

不服气?这是一些测试代码 - 我使用了具有完整优化的WATCOM C,而无需使用_cdecl,因此由此产生的拆卸将尽可能干净:

----[ 测试.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

----[ 结束 ]-------------------------------------------------------- ----------------------

使用按位运算符: & |

设置最后一位 000b:

foo = foo | 001b

检查最后一位 foo:

if ( foo & 001b ) ....

清除最后一位 foo:

foo = foo & 110b

我用了 XXXb 为了清楚起见。您可能会使用十六进制表示,具体取决于您打包位的数据结构。

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

例子:

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)

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

ETC。

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

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 第一个,但这可能取决于实现。另外,验证编译器处理程序字段如何跨越字节边界。

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

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

然而,在一次基于示波器的调试过程中,我惊讶地发现,与直接将值写入微的 PORTnSET / PORTnCLEAR 寄存器相比,这些方法在 CPU 周期中具有相当大的开销,这在存在紧密循环/高的情况下产生了真正的差异-频率 ISR 的切换引脚。

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

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

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

检查任意类型变量中任意位置的位:

#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 对其进行了测试。可以制作类似的宏来设置和清除位。与这里的许多其他解决方案相比,该解决方案的主要区别在于它适用于几乎任何类型变量的任何位置。

该程序是将任意数据位从 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);
        }
    }
}

如果您需要做很多事情,您可能需要使用遮罩,这将使整个过程更快。以下函数非常快并且仍然灵活(它们允许在任何大小的位图中进行位旋转)。

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 等在内存中正确地相互映射(小端处理器比大端处理器“更好”的主要原因,啊,我感觉一场激烈的战争即将来临)在...)。

用这个:

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

    return num;
}

扩展于 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;
}

如果您想使用 C 编程来执行所有操作 Linux内核 那么我建议使用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计算机上,也有助于在处理器之间保持连贯性。

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] =1, , 或者 IsGph[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 个值。

我曾经使用这种方法来查找重复的贷款记录,其中 Loan_number 是 ISAM 键,使用 6 位贷款编号作为位数组的索引。速度非常快,8 个月后,证明我们从中获取数据的主机系统实际上出现了故障。位数组的简单性使得人们对其正确性的信心非常高——例如与搜索方法相比。

使用定义的运算符之一 这里.

要设置一点,使用 int x = x | 0x?; 在哪里 ? 是二进制形式的位位置。

以下是我使用的一些宏:

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)

使用的变量

int value, pos;

价值-数据
pos - 我们有兴趣设置、清除或切换的位的位置。

稍微设置一下:

value = value | 1 << pos;

清除一点:

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

稍微切换一下:

value = value ^ 1 << pos;

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

为了解决尝试形成掩码时常见的编码陷阱:
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;

与大多数位操作一样,最好使用 未签名 类型而不是 那些

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);
}

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;}
}

尝试使用 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;
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top