位运算

C语言拥有操作数据位的能力,这使我们能以少量的空间表达更多的信息。

比如我们要记录12个设备的开关状态,即使使用char类型的数组,也需要12个字节。

char dev_online_states[12] = { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1 };

而通过使用数据位,仅需要2个字节就能做到。

short dev_online_states = 0b011101000011; // 以2进制方式初始化

我们也可以将多个位组合起来,来表达更丰富的状态。

+-----------------------+
| 7 6 5 | 4 3 | 2 1 | 0 | --> 比特位
--------|-----|-----|---|
| 1 0 1 | 0 0 | 1 0 | 1 | --> 状态值
+-----------------------+

比如这里使用1个字节来表示设备的运行时状态,其中Bit0表示设备是否就绪;Bit1-2表示故障码,当前故障码是0b10(2),这可能代表设备发出了某些警告;Bit3-4表示其它某些状态;Bit5-7当前值是0b101(5)。

C语言提供了6个位运算操作符。按位与(&)、按位或(|)、按位异或(^)、按位取反(~)、左移位(<<)、右移位(>>)。所有的位运算都是指二进制数的位运算,它们只能作用于整型操作数。

这里我们以一个有换肤、最小化、最大化、关闭按钮的窗口示例,看看如何通过位运算来定制窗口外观。如:

换肤 最小化 最大化 关闭 数值 描述
0 0 0 0 0x00 窗口没有任何按钮
0 1 0 1 0x05 窗口有最小化、关闭按钮
1 1 0 1 0x0D 窗口有换肤、最小化、关闭按钮

其中,按钮的状态值定义如下:

#define BUTTON_CLOSE    0x01    // (0000 0001)
#define BUTTON_MINIMUM  0x02    // (0000 0010)
#define BUTTON_MAXIMUM  0x04    // (0000 0100)
#define BUTTON_SKIN     0x08    // (0000 1000)
#define BUTTON_ALLS     0xFF    // (1111 1111)

按位与(&、&=)

当两个数对应的位都为1时,结果为1,否则为0;按位与可用于测试某位是否被置位。

   a | 1000 1111
 & b | 1010 0100
-----|-----------
 =   | 1000 0100

程序为窗口设定了指钮状态3,通过按位与运算,测试出窗口拥有最小化与关闭按钮。

#include <stdio.h>

void test_buttons(unsigned char buttons)
{
    if (buttons & BUTTON_SKIN)    printf("skin ");
    if (buttons & BUTTON_MINIMUM) printf("min ");
    if (buttons & BUTTON_MAXIMUM) printf("max ");
    if (buttons & BUTTON_CLOSE)   printf("close");
}

int main()
{
    test_buttons(3);
    return 0;
}

glimix.com

按位或(|、|=)

当两个数对应的位至少有1个是1时,结果为1,否则为0;按位或可用于设置指定位。

   a | 1000 1111
 | b | 1010 0100
-----|-----------
 =   | 1010 1111

让我们看看如何通过按位或一步一步为窗口增加按钮。

int main()
{
    // 初始窗口没有按钮
    unsigned char buttons = 0;
    printf("buttons: %u\n", buttons);

    // 为窗口启用关闭按钮
    buttons = buttons | BUTTON_CLOSE;
    printf("buttons: %u\n", buttons);

    // 追加上最小化按钮
    buttons |= BUTTON_MINIMUM;
    printf("buttons: %u\n", buttons);

    printf("\n");
    test_buttons(buttons);
    printf("\n");

    // 加上最大化与换肤按钮
    buttons |= (BUTTON_SKIN | BUTTON_MAXIMUM);
    test_buttons(buttons);
    printf("\n");

    return 0;
}

glimix.com

取反(~)与异域(^)

取反(~): 此操作只对一个数据运算,对数值的所有位反转,将1变0,0变1。

  a   | 1000 1111
------|-----------
 ~a = | 0111 0000

按位异或(^):当两个数对应的位不相同时为1,否则为0。

   a | 1000 1111
 ^ b | 1010 0100
-----|-----------
 = c | 0010 1011

让我们看看这些运算对窗口按钮的影响。

int main()
{
    // 初始窗口有最大化、关闭按钮
    unsigned char buttons = 0b101;
    printf("---- ");
    test_buttons(buttons);

    // 异或后窗口有换肤、最小化按钮
    buttons ^= BUTTON_ALLS;
    printf("\nXOR: ");
    test_buttons(buttons);
    printf("\n\n");

    // 重置窗口有所有按钮
    printf("---- ");
    buttons = BUTTON_ALLS;
    test_buttons(buttons);

    // 屏蔽(取消)最大化按钮
    buttons &= ~BUTTON_MAXIMUM;
    printf("\nNOT: ");
    test_buttons(buttons);

    return 0;
}

glimix.com

仔细观察异或操作,你会发现异或的结果与任一原数再异或,会得到另一个操作数,利用这个特性,我们可以不借用中间变量交换两个整数值。

#include <stdio.h>

int main()
{
    int a = -1234, b = 925;
    printf("before swap: a = %5d b = %5d\n", a, b);

    a ^= b;
    b ^= a;
    a ^= b;
    printf("after  swap: a = %5d b = %5d\n", a, b);

    return 0;
}

glimix.com

左移(<<)与右移(>>)

左移运算将整型数值按二进制位向左移动N位,高位移出后,低位补0。

    数字站在桌子边        左移三位      掉下去三个
============================================================================
    0 1 1 0 0 0 1 1      << 3         0 0 0 1 1 0 0 0
    +--------------                   +---------*****
    |                               1 |             0-------- 补零
    |                             1   |           0---------- 补零
    |                           0     |         0------------ 补零

右移运算有所不同:

对有符号数,C标准并没有明确地指定是使用逻辑右移还是算术右移,但大多数的机器都使用算术右移。示例展示了移位在有无符号数间的差异。

无符号数 3(00000011)

有符号正数 +3(00000011)

有符号负数 -3(11111101)

#include <stdio.h>

int main()
{
    unsigned char x = 3;
    signed char y = 3;
    signed char z = -3;

    printf("unsigned char: 3\n"
           "===========================\n"
           "<< 1 = %d\n"
           ">> 1 = %d\n",
           x << 1,
           x >> 1);

    printf("\n");
    printf("signed char: 3\n"
           "===========================\n"
           "<< 1 = %d\n"
           ">> 1 = %d\n",
           y << 1,
           y >> 1);

    printf("\n");
    printf("signed char: -3\n"
           "===========================\n"
           "<< 1 = %d\n"
           ">> 1 = %d\n",
           z << 1,
           z >> 1);

    return 0;
}

glimix.com

陕ICP备2025078817号-1 陕公网安备61011202001108号