关于c ++:如何设置,清除和切换一个字节?

How do you set, clear, and toggle a single bit?

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


设置位

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

1
number |= 1UL << n;

这将设置number的第7位。如果要将1位等设置为n-1,如果要设置n位,则n应为零。

如果numberunsigned long宽,则使用1ULL;只有在评估1UL << n后,1UL << n才被提升,因为在评估1UL << n后,1UL << n的移动超过long的宽度是不确定的行为。这同样适用于所有其他示例。

澄清一点

使用位与运算符(&来清除位。

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

这将清除number的第7位。必须使用按位非运算符(~)反转位字符串,然后再反转它。

切换一点

xor运算符(^可用于切换位。

1
number ^= 1UL << n;

这将切换number的第7位。

检查一点

你没有要求,但我还是加上一点。

要检查一点,将数字n右移,然后按位和它:

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

这将把number的第7位的值放入变量bit

将第n位更改为x

将EDOCX1第7位第10位设置为EDCOX1、10或EDCOX1×2,可在2的补码C++实现中实现:

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

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

为了使它独立于2的补码否定行为(其中EDCOX1×39)已经设置了所有位,与1的补码或符号/大小C++实现不同,使用无符号否定。

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

1
2
unsigned long newbit = !!x;    // Also booleanize to force 0 or 1
number ^= (-newbit ^ number) & (1UL << n);

使用无符号类型进行可移植的位操作通常是一个好主意。

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

(number & ~(1UL << n))将清除n第位,(x << n)n第位设置为x

一般来说,不复制/粘贴代码也是一个好主意,很多人使用预处理器宏(比如下面的社区wiki答案)或某种封装。


使用标准C++库:EDCOX1×0。

或者是增强版:boost::dynamic_bitset

不需要自己滚:

1
2
3
4
5
6
7
8
9
10
11
12
13
#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;
}
1
2
[Alpha:] > ./a.out
00010

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


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

1
2
3
4
5
6
7
struct bits {
    unsigned int a:1;
    unsigned int b:1;
    unsigned int c:1;
};

struct bits mybits;

定义一个3位字段(实际上是三个1位FELD)。位操作现在变得简单了一点(haha):

设置或清除一点:

1
2
mybits.b = 1;
mybits.c = 0;

要切换一点:

1
2
3
mybits.a = !mybits.a;
mybits.b = ~mybits.b;
mybits.c ^= 1;  /* all work */

检查一点:

1
if (mybits.c)  //if mybits.c is non zero the next line below will execute

这只适用于固定大小的位字段。否则,你必须借助于前几篇文章中描述的位旋转技术。


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

1
2
3
4
5
6
7
8
9
10
11
12
/* 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来命名位:

1
2
3
4
5
6
enum ThingFlags = {
  ThingMask  = 0x0000,
  ThingFlag0 = 1 << 0,
  ThingFlag1 = 1 << 1,
  ThingError = 1 << 8,
}

然后在后面使用这些名称。即写

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

设置、清除和测试。这样,您就可以将魔法数字隐藏在代码的其余部分。

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


从snip-c.zip的bitops.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
**  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,则此表达式将评价为:

1
    0000 0000 0000 0000 0000 0000 0000 0001 binary.

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

1
    0000 0000 0000 0000 0000 0001 0000 0000 binary.

换句话说,它只创建一个0的字段,其中1位于指定的位置。唯一棘手的部分是bitclr()宏,我们需要在其中设置字段1中的单个0位。这是通过使用1来完成的。用颚化符(~)运算符表示的同一表达式的补码。

一旦创建了遮罩,它就会按照您的建议应用于参数,使用位与(&;)或()和xor(^)运算符。自从面具之后是long类型,宏在char、short、int和或是长的。

最重要的是,这是对整个类问题。当然,重写每当您需要一个,但为什么要这样做?记住,宏替换发生在预处理器,因此生成的代码将反映以下事实:被编译器视为常量-也就是说,使用每当你需要做的时候,你都可以使用通用的宏来"重新发明轮子"。钻头操作。

不服气?以下是一些测试代码-我使用了watcom C并进行了完全优化如果不使用_cdecl,则所产生的拆卸将与可能的:

——[测试c]——————————————————————————————————————————————————————————————————————————————————————————————————————————————————--

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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;
}

----[测试输出(已分解)]----------------------------------------

1
2
3
4
5
6
7
8
9
10
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中的最后一位:

1
foo = foo | 001b

检查foo中的最后一位:

1
if ( foo & 001b ) ....

清除foo中的最后一位:

1
foo = foo & 110b

为了清晰起见,我使用了XXXb。根据封装位的数据结构,您可能会使用十六进制表示。


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

例子:

1
2
value is 0x55;
bitnum : 3rd.

使用&运算符检查位:

1
2
3
4
5
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)

切换或翻转:

1
2
3
4
5
0101 0101
^
0000 1000
___________
0101 1101 (Flip the third bit without affecting other bits)

|运算符:设置位

1
2
3
4
5
0101 0101
|
0000 1000
___________
0101 1101 (set the third bit without affecting other bits)

这是我最喜欢的位算术宏,它适用于从unsigned charsize_t的任何类型的无符号整数数组(这是应该有效使用的最大类型):

1
2
#define BITOP(a,b,op) \
 ((a)[(size_t)(b)/(8*sizeof *(a))] op ((size_t)1<<((size_t)(b)%(8*sizeof *(a)))))

设置一点:

1
BITOP(array, bit, |=);

澄清一点:

1
BITOP(array, bit, &=~);

要切换一点:

1
BITOP(array, bit, ^=);

测试一点:

1
if (BITOP(array, bit, &)) ...

等。


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

1
2
3
4
5
6
7
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,但这可能与实现有关。另外,验证编译器如何处理跨越字节边界的字段。

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


因为它被标记为"嵌入式",所以我假设您使用的是微控制器。以上所有建议都是有效的(读-修改-写、联合、结构等)。

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

对于不熟悉的人:在我的例子中,micro有一个反映输出管脚的通用管脚状态寄存器portn,因此,portn=bit to _set会导致对该寄存器的读-修改-写操作。但是,portnset/portnclear寄存器采用"1"表示"请将此位设为1"(设置)或"请将此位设为零"(清除),而"0"表示"别管脚"。因此,您最终得到了两个端口地址,这取决于您是在设置还是清除位(并不总是方便的),而是更快的反应和更小的汇编代码。


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

1
2
3
4
#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)))


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

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

样品使用情况:

1
2
3
4
5
6
7
8
9
10
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
"
, ix, bit_test(arr, ix));

    return 0;
}

笔记:这是设计为快速(考虑到它的灵活性)和非分支。在编译Sun Studio 8时,它会产生高效的SPARC机器代码;我还使用AMD64上的MSVC++2008测试了它。可以制作类似的宏来设置和清除位。与其他许多解决方案相比,此解决方案的关键区别在于它适用于几乎任何类型变量中的任何位置。


如果你在做很多琐碎的事情,你可能会想用面具,这会使整个事情更快。以下函数速度非常快,而且仍然很灵活(它们允许在任意大小的位图中进行位旋转)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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",请执行以下操作:

1
TSetBit( n, &my_int);

由您来确保位号在您通过的位映射的范围内。请注意,对于字节、字、双字、qwords等的小endian处理器,它们在内存中相互正确映射(小endian处理器比big endian处理器"更好"的主要原因是,啊,我感到一场火焰大战即将来临…)。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
    unsigned int data = 0x000000F0;
    int bitpos = 4;
    int bitvalue = 1;
    unsigned int bit = data;
    bit = (bit>>bitpos)&0x00000001;
    int invbitvalue = 0x00000001&(~bitvalue);
    printf("%x
"
,bit);

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

使用此:

1
2
3
4
5
6
7
8
9
int ToggleNthBit ( unsigned char n, int num )
{
    if(num & (1 << n))
        num &= ~(1 << n);
    else
        num |= (1 << n);

    return num;
}


扩展bitset答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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;
}

如果您想在Linux内核中使用C编程来执行所有这些操作,那么我建议使用Linux内核的标准API。

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

1
2
3
4
5
6
7
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计算机上,这些都是原子的,并且是有用的保持处理器之间的一致性。


VisualC 2010以及其他许多编译器都直接支持内置的位操作。令人惊讶的是,这是可行的,即使sizeof()操作符也能正常工作。

1
2
3
4
5
6
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使设置和清除bools变得容易。

要查找无法打印的字符…

1
2
3
4
5
6
7
8
9
10
//  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个月后,证明了我们从中获取数据的主机系统实际上出现了故障。位数组的简单性使得对其正确性的信心非常高,例如与搜索方法相比。


使用此处定义的其中一个运算符。

为了设置一个位,使用int x = x | 0x?;,其中?是二进制形式的位位置。


变量使用

1
int value, pos;

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

1
value = value | 1 << pos;

澄清一点

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

拨动一点

1
value = value ^ 1 << pos;

以下是我使用的一些宏:

1
2
3
4
5
6
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)

How do you set, clear, and toggle a single bit?

要在试图形成屏蔽时解决常见的编码缺陷:1并不总是足够宽

number是比1更宽的类型时,会发生什么问题?x可能太大,不利于1 << x的移位,导致行为不明确(ub)。即使x不是太大,~可能不会翻转足够多的有效位。

1
2
3
4
5
6
7
8
9
10
// 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,并让编译器进行优化。

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

或CAST-这使得编码/审查/维护问题保持正确和最新的CAST。

1
number |= (type_of_number)1 << x;

或者通过强制执行一个与number类型相同宽度的数学运算来轻轻地提升1

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

与大多数位操作一样,最好使用无符号类型,而不是有符号类型。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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模板化版本(放入报头):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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位:

1
2
3
4
5
6
7
8
char bitfield;

// Start at 0th position

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

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

1
2
3
4
5
6
7
8
9
10
11
12
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;
}