位运算基础应用

张开发
2026/4/17 22:53:38 15 分钟阅读

分享文章

位运算基础应用
一、位运算概述在计算机系统中数据最终都以二进制形式存储。无论是一个普通整型变量还是一个寄存器值处理器看到的本质都是由若干个 0 和 1 组成的二进制位序列。位运算就是直接针对这些二进制位进行操作的一类运算。位运算和加减乘除这类算术运算不同位运算并不优先关注“数值大小”的数字意义而是直接关注一个数据在二进制层面的每一位状态。例如某一位是 1 还是 0某几位组成的字段代表什么含义某一位是否需要被置位或清零这些都属于位运算处理的范畴。在C语言中位运算主要包括以下几类运算符名称说明按位与两位都为 1结果才为 1|按位或两位任一位为1结果为1^按位异或两位不同结果为 1相同为 0~按位取反将每一位 0/1 反转左移所有位整体左移若干位右移所有位整体右移若干位位运算在嵌入式开发中是非常基础的一项能力因为底层硬件配置本身就是按位定义的。比如这个是STM32F4xx 的GPIO位定义从中我们可以看出 31:16 是预留位而 15:0 则是每个GPIO引脚的输出类型每个位分别对应引脚 Px0、Px1、Px2 ··· Px14、Px15这种情况下我们就可以通过位运算对 Px0 ~ Px15 中的特定位进行读取、修改和组合。从这里我们也可以看出位运算比较核心的价值可以精确控制单个 bit 或一组 bit节省存储空间可以用一个字节表达多个状态非常适合寄存器、协议、标志位这类底层数据结构的处理。所以位运算不是“语法技巧”而是底层开发中的常规工具。二、位运算基础原理一如何按位看数据给一个8位十进制数据 uint8_t a 13转换成二进制为 0000 1101131x200x211x221x23如果按 bit 编号一般从低位到高位编号为 bit0 ~ bit7:bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 0 0 0 0 1 1 0 1在这里可以看到 bit01、bit10、bit21、bit31。通过这也可以看出位运算本质上就是对这样的位序列逐位进行逻辑处理。二按位与 运算规则两个操作数对应位都为1结果才为1。0 0 0 0 1 0 1 0 0 1 1 1下面举个具体示例来进行说明uint8_t a 0b1101; uint8_t b 0b1011; uint8_t c a b;对 a 与 b 逐位对比a 1 1 0 1 b 1 0 1 1 ab: 1 0 0 1所以最终 c 的结果就是 0b1001这就是按位与的执行逻辑。在实际应用中按位与最典型的用途不是“求值”而是屏蔽不关心的位也就是后面常说的 mask 操作。三按位或 |运算规则对应位只要有一个是1结果就是1。0 | 0 0 0 | 1 1 1 | 0 1 1 | 1 1继续按照按位与中的示例来进行说明a: 1 1 0 1 b: 1 0 1 1 a|b: 1 1 1 1通过按位或运算后最终 c 的结果变为了 0b1111在实际使用中我们也经常通过按位或去达到对某一bit进行置位的目的四按位异或 ^运算规则对应位不同为1相同为0。0 ^ 0 0 0 ^ 1 1 1 ^ 0 1 1 ^ 1 0继续按照按位与中的示例进行说明a: 1 1 0 1 b: 1 0 1 1 a^b: 0 1 1 0通过按位异或运算后最终 c 的结果变成了 0b0110。所以对按位异或可以总结出以下通式//x表示任意整数变量 x ^ 0 x x ^ x 0五按位取反 ~运算规则每一位都翻转。1 0000 0001 ~1 1111 1110 0 0000 0000 ~0 1111 1111从这个示例可以看出对 1 或 0 进行 ~ 时是针对其数据类型的整个类型宽度进行取反的而不是只关心显式的那几位bit比如//uint8_t 1 0000 0001 ~1 1111 1110 0 0000 0000 ~0 1111 1111 //uint16_t 1 0000 0000 0000 0001 ~1 1111 1111 1111 1110 0 0000 0000 0000 0000 ~0 1111 1111 1111 1111六左移 运算规则将所有 bit 整体向左移动 n 位高位溢出的bit丢弃低位补0。1010 1010 1 0101 0100对于无符号数在不溢出的前提下x n ≈ x * 2^n比如5 1 10 5 2 20 5 3 40这个并不是所有场景都可以将左移等同乘法只要发生溢出高位被丢弃结果就不再等价。比如//二进制1010 1010 十进制170 1010 1010 1 /* 按照左移乘法的公式来算8位整数只能表示0~255 所以但从范围边界来看移位后的结果也一定发生 了截断即大于0小于255 */ 170 * 2 340 /* 移位后可以得到0101 0100再转 十进制84而84与340不等所以若 发生溢出就无法再使用去做乘法。 */ 1010 1010 1 0101 0100而在C标准中如果有符号数左移后溢出导致结果不能表示就会产生未定义行为。所以在实际工程中一般更好是对 unsigned 类型的数据进行左移操作。七右移 运算规则将所有 bit 整体向右移动 n 位低位移除的 bit 被丢弃。0000 1101 1 0000 0110可以看到0000 1101 最右边的 1 被移出丢弃后变成了 0000 0110。对于无符号数//x是任意无符号整数 x n ≈ x / 2^n需要注意右移与左移不同右移有两种情况一种是逻辑右移一种是算术右移而左移则只有一种情况并没有做区分。那么为什么右移会出现这两种情况呢主要还是因为右移涉及到了有符号整形中的符号变化问题。在无符号数unsigned中右移整形只需要在高位补0即可比如unsigned char a 0b10101010; a 1 0b0101 0101;这是逻辑右移。在有符号signed中右移整形大多数编译器会在高位补符号位比如1110 0000 1 //若是负数高位补1 1111 0000 //若是正数高位补0 0111 0000这是算术右移。针对左移和右移可做如下总结运算补位特殊情况补0可能溢出 unsigned补0逻辑右移 signed补符号位算术右移补充一句在补码系统中左移相当于乘2右移相当于除2但只有在不发生溢出且使用算术右移时才能保持符号正确。所以在实际开发中更推荐使用无符号数unsigned进行位运算。八运算符优先级位运算符在整体运算符体系中的优先级如下优先级顺序运算符名称高~按位取反移位按位与^按位异或低|按位或位运算符与其他运算符的优先级关系如下优先级运算符名称高~!单目运算符*/%乘除-加减移位比较!相等按位与^按位异或|按位或逻辑与低||逻辑或所以总体看下来就是位运算优先级低于算术运算但高于逻辑运算。三、位运算常用操作这一部分时位运算在工程中最常用的四类基本动作置位、清零、读取、翻转。一置位在对寄存器进行操作时我们通常需要将寄存器的第 n 个 bit 置1此时我们可以reg | (1U n);比如现在我想把 reg 的 bit3 置1其他位保持不变就可以reg | (1U 3);下面我会对这个 reg 的 bit3 置位操作进行拆解。首先 1U 3 可以得到1U 0000 0001 - 1U 3 - 0000 0001 3 - 0000 1000然后再和原值做按位或对应位只要有一个是1结果就是1原值 1010 0010 掩码 0000 1000 结果 1010 1010从运算结果来看实现了将 bit3 强制置1的效果。二清零清零即将第 n 个 bit 清0。reg ~(1U n);比如我现在想把 reg 的 bit3 清零其他位保持不变就可以reg ~(1U 3);下面我会依据这个 reg 的 bit3 清零操作进行拆解首先 1U 3 可以得到1U 0000 0001 - 1U 3 - 0000 0001 3 - 0000 1000然后对(1U 3)进行取反将每一位0/1反转~(1U 3) - ~0000 1000 - 1111 0111最后再和原值做按位与两位都为 1结果才为 1原值1010 1111 掩码1111 0111 结果1010 0111只有 bit3 被清0。三读取读取即判断第 n 个 bit 是否为1。if(reg (1U n)) { //bit n 为1 }比如if(reg (1U 5)) { //第五位标志有效 }意思是如果 bit5 原本是1那么与之后结果非0但是若 bit5 原本为0那么结果为0。四翻转翻转即将第 n 个 bit 翻转。reg ^ (1U n);比如reg ^ (1U 3);下面我将把 reg 的 bit3 翻转操作进行拆解。首先是 1U 3 可以得到1U 3 - 0000 0001 3 - 0000 1000然后和原值做按位异或两位不同结果为 1相同为 0情况1bit3 为1 原值1010 1010 掩码0000 1000 结果1010 0010 情况2bit3 为0 原值1010 0010 掩码0000 1000 结果1010 1010可以看到情况1中原值的 bit3 原来是1然后被翻转为了0情况2原值的 bit3 为0然后被翻转为了1。五多位同时操作不是只有单 bit 才能操作一组 bit 同样可以使用位运算进行处理。比如1设置低四位为1reg | 0x0F;2清低四位reg ~0x0F;(3)读取低四位value reg 0x0F;

更多文章