位运算
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;
}

按位或(|、|=)
当两个数对应的位至少有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;
}

取反(~)与异域(^)
取反(~): 此操作只对一个数据运算,对数值的所有位反转,将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;
}

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

左移(<<)与右移(>>)
左移运算将整型数值按二进制位向左移动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------------ 补零
右移运算有所不同:
- 对于无符号数,同左移一样,执行移位和补零操作,这称为逻辑右移;
- 对于有符号数在移位时保持符号位不变,正数补0,负数补1,这称为算术右移。
对有符号数,C标准并没有明确地指定是使用逻辑右移还是算术右移,但大多数的机器都使用算术右移。示例展示了移位在有无符号数间的差异。
无符号数 3(00000011)
- 3<<1:最左边的位移掉,向最右边的移进来的位补零,结果为 6(00000110)。
- 3>>1:由于是无符号数,所以逻辑右移,最右边一位移掉,最左边移进来的位补零,结果为 1(00000001)。
有符号正数 +3(00000011)
- +3<<1:最左边的位移掉了,最右边的移进来的位补零。结果为 6(00000110)。
- +3>>1:执行算术右移,最高位补符号0,结果为 1(00000001);逻辑右移结果同样为1。
有符号负数 -3(11111101)
- -3<<1:最左边的位移掉了,最右边的移进来的位补零。结果为 -6(11111010)。
- -3>>1:执行算术右移,最高位补符号1,结果为 -2(11111110);逻辑右移结果为 126(01111110)
#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;
}
