位操作
1. 位与二进制
1.1 计算机中的位
- 定义:
- 位是 最小的 计算机 数据存储 单位
- 每一个位存储一个二进制码(记作 0 或 1)
- 对于 整型 数据来说,每个计算机位对应这个整数二进制表示中的一个位
- 8 位构成一个 字节,因此 在常见的实现中,
int
类型有 4 字节(32 位),long long
有 8 字节(64 位) - 关于进制的知识,可以参考 进制
1.2 整数的二进制表示(以一字节为例)
- 无符号:即
unsigned char
类型,二进制表示中,所有位都用于表示数值,范围在0 ~ 255
之间 - 有符号:即
signed char
类型:- 也许有符号数最简单的表示方法是使用 1 位(最高位)用于表示符号,其余位用于表示数值本身,但这么做有个问题:
+0
与-0
是两个数,这显然是不合理的,因此,在计算机中,有符号数通常使用 二进制补码 来表示(这也是当今最常用的系统) - 二进制补码 表示法:最高位设置
0
,用后 7 位表示0 ~ 127
。最高位设置1
,则表示负数,具体方法如下:- 首先,从一个 9 位组合
100000000
减去一个负数的位组合,结果是该负数的量。例如:有符号数100000000
,其位组合表示一个无符号数128
,因为100000000 - 10000000 = 10000000
,即256 - 128 = 128
。 - 再由于其最高位为
1
,因此,该例的有符号数表示-128
。 - 以此类推:
10000001
表示-127
,10000010
表示-126
,该方法可以表示-128 ~ +127
的所有整数。
- 首先,从一个 9 位组合
- 也许有符号数最简单的表示方法是使用 1 位(最高位)用于表示符号,其余位用于表示数值本身,但这么做有个问题:
注:逻辑代数中,0 和 1 不表示数量的大小,而是表示两种 对立的状态
2. 位操作
只能使用 整型 数据进行位操作,不能使用
**数据;浮点型**
2.1 位操作的种类
下面的例子使用无符号 8 位整数进行演示:
按位与 (&):对应位都为
1
时,结果才为1
:C(10011010) & (10110110) // 表达式 10010010 // 结果值
当然 C 中有一个按位与和赋值结合的运算符:
&=
按位或 (|):对应位只要有一个为
1
或都为1
,结果就为1
:C(10011010) | (10110110) // 表达式 10111110 // 结果值
当然 C 中也有一个按位或和赋值结合的运算符:
|=
按位异或 (^):对应位不同时,结果为
1
:C(10011010) ^ (10110110) // 表达式 00101100 // 结果值
当然 C 中也有一个按位异或和赋值结合的运算符:
^=
按位取反 (~):
1
变0
,0
变1
:C~(10011010) // 表达式 01100101 // 结果值
按位左移 (<<):
C(10011010) << 2 // 表达式 01101000 // 结果值
当然 C 中也有一个按位左移和赋值结合的运算符:
<<=
按位右移 (>>):
C(10011010) >> 2 // 表达式,有符号 00100110 // 在某些实现中的结果 11100110 // 在另一些实现中的结果
在不同实现中,对有符号数的右移操作,其左端填补数可以为
1
也可以为0
。大多数实现中,左端填补数和原符号位相同,确保正负不变。C(10011010) >> 2 // 表达式,无符号 00100110 // 结果值
当然 C 中也有一个按位右移和赋值结合的运算符:
>>=
2.2 位操作的应用
注:在之后的实际项目里,尤其是对 硬件 开发上,位操作会经常用到 在下面的示例中,使用无符号整数来演示,即
unsigned int
类型,默认你们已经引入stdint.h
头文件:
#include <stdint.h>
或者,也可以使用 typedef
定义一些无符号整数类型,如下:
typedef unsigned long long uint64_t;
typedef unsigned int uint32_t;
typedef unsigned short int uint16_t;
typedef unsigned char uint8_t;
示例中的 二进制字面量 只有 8 位,这其实没什么问题,因为二进制位数是由类型决定的,而不是字面量的位数,因此,**字面量位数 ** 可以比 该字面量类型位数 少,也可以比 该字面量类型位数 多。但需要注意的是,二进制存储是 从右往左 的,因此,如果 字面量位数 比 该字面量类型的位数 多,那么,字面量左边的位会被丢弃,如下:
uint32_t flag = 0b1111111100000000000000000000000000000000;
// 40位的字面量,如果 int 为 32 位,那么 flag 只能存储 32 个二进制位
// 可能造成如下结果:
// flag = 0b00000000000000000000000000000000(flag 最左的 8 位被丢弃)
若是字面量位数比该字面量类型的位数少,那么,字面量左边的位会被补零。
掩码 ( MASK )
定义:掩码是一个 二进制数,用于 屏蔽 另一个二进制数的特定位
可以这样类比:掩码就像一个 滤网 ,只有掩码中的 1 是孔,把flag的对应位置给透出来,其余位置被 0 给堵上
下面举个简单的例子:
C//typedef unsigned char uint8_t; uint8_t flag = 0b10101010; //定义MASK,0号位为1,其余位为0 uint8_t mask = 0b00000010; //掩码操作 flag = flag & mask; //此时 flag = 0b00000010
打开位 ( SET )
定义:打开位,就是将特定位设置为
1
,同时保持其它位不变使用
|
运算符和掩码,翻译成人话就是:任何位和1
组合,结果都是1
;任何位和0
组合,结果都是该位本身以上一节的 mask(只有
1
号位为 1)为例:Cuint8_t flag = 0b10101001; flag |= mask; //此时 flag = 0b10101011
根据 mask 中为
1
的位, flag 中对应位设置为1
,其余位不变
清空位 ( CLEAR )
定义:清空位,就是将特定位设置为
0
, 同时保持其它位不变使用
&
和~
运算符与掩码,看代码:以上一节的 mask(只有
1
号位为 1)为例:Cuint8_t flag = 0b10101011; flag &= ~mask; //此时 flag = 0b10101001
根据 mask 中为
1
的位, flag 中对应位设置为0
,其余位不变
切换位 ( TOGGLE )
定义:切换位,就是将特定位取反,同时保持其它位不变
使用
^
运算符和掩码,即:任何位和1
组合,结果都是该位取反;任何位和0
组合,结果都是该位本身以新的 mask(
0b11110000
)为例:Cuint8_t flag = 0b10101011; flag ^= mask; //此时 flag = 0b01011011
根据 mask 中为
1
的位, flag 中对应位切换,其余位不变
读取位 ( READ )
定义:读取位,就是只读取特定位的值
使用
&
运算符和掩码,代码如下:以上一节的 mask(只有
1
号位为 1)为例:Cuint8_t flag = 0b10101011; if ((flag & mask) == mask) { printf("1号位为1。"); }
根据 mask 中为
1
的位, 在 flag 中只读对应。