位运算基础应用(一)如何巧妙融入?
摘要:本文对 C 语言中的常见位运算进行了系统整理与说明,包括按位与、按位或、按位异或、按位取反以及左移和右移运算。通过结合二进制示例,介绍了各类位运算的基本规则、运算过程及其数学含义,并分析了移位运算在发生溢出或涉及有符号类型时可能出现的行为差
一、位运算概述
在计算机系统中,数据最终都以二进制形式存储。无论是一个普通整型变量,还是一个寄存器值,处理器看到的本质都是由若干个 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 1101(13=1x20+0x21+1x22+1x23),如果按 bit 编号,一般从低位到高位编号为 bit0 ~ bit7:
bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
0 0 0 0 1 1 0 1
在这里可以看到 bit0=1、bit1=0、bit2=1、bit3=1。通过这也可以看出位运算本质上就是对这样的位序列逐位进行逻辑处理。
(二)按位与 &
运算规则:两个操作数对应位都为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
a&b: 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
(五)按位取反 ~
运算规则:每一位都翻转。
