C语言之位操作

在《C语言之结构体》的最后,我们提到了有关位域的概念,我们可以发现C语言强大到精准到的控制能力。这篇我们就来讲讲位操作

C语言不仅可以按位存储,也可以进行一些操作。在这之前,我们来回顾一下,C语言的逻辑运算符

逻辑运算符 中文名
&&
||
!

实际上,C语言的逻辑运算符是有返回值的,只不过这个返回值有些特殊,是布尔类型的(C++中才有)。而C语言中是没有明确规定存在布尔类型,所以C语言中就用底层来实现——1代表True,0代表False。

因此:

a = 50;
a == 50;

第二行的返回值就是1(True)

而:

a = 50; b = 100;
a == 50 && b == 50;

第二行的返回值就是0(False)

正因为是这种实现方式,因此在制造恒循环的时候,可以这么写:

while 1

1这个表达式就相当于True,等价于这个表达式永远都是返回True,循环永远得以执行。

位运算

C语言中,位操作通常都是针对二进制。因此,和逻辑运算符有点类似:

位运算符 优先级 中文名 用法
~ 按位取反 ~a
& 按位与 a&b
^ 按位异或 a^b
| 最低 按位或 a|b

按位取反

按位取反实际上就是 1 变 0,0 变 1。

举个例子:

1111000000001111\begin{array}{ccc} _{按位取反}&11110000\\ \hline&00001111\\ \end{array}

按位与

按位与就是全 1 则 1,有 0 则 0。

举个例子:

110011001111000011000000\begin{array}{ccc}&11001100\\_{按位与}&11110000\\\hline&11000000\end{array}

按位异或

按位异或就是同则为 0,不同为 1。

举个例子:

110011001111000000111100\begin{array}{ccc}&11001100\\_{按位异或}&11110000\\\hline&00111100\end{array}

按位或

按位或就是有 1 则 1,全 0 则 0。

举个例子:

110011001111000011111100\begin{array}{ccc}&11001100\\_{按位或}&11110000\\\hline&11111100\end{array}

和赋值号结合

除了按位取反是单目运算符,其余的三个都可以和赋值号结合,可以使代码更加简洁:

//Example 01
# include <stdio.h>

int main(void)
{
    int plus = 0b11110000;
    int ori1 = 0b11001100;
    int ori2 = 0b11001100;
    int ori3 = 0b11001100;
    
    ori1 &= plus;
    ori2 ^= plus;
    ori3 |= plus;
    
    printf("ori1 = 0x%x\n", ori1);
    printf("ori2 = 0x%x\n", ori2);
    printf("ori3 = 0x%x\n", ori3);
    
    return 0;
}

结果为:

//Consequence 01
ori1 = 0xc0
ori2 = 0x3c
ori3 = 0xfc

转成二进制:

//Consequence 01 in Binary
ori1 = 0b11000000
ori2 = 0b00111100
ori3 = 0b11111100

移位运算符

除了上述的逻辑位运算以外,C语言还提供了移位运算符。

左移

左移运算符(<<)就是将二进制向左移动若干位。

比如:

0b11001101 << 2;//左移2位

<<21100110100110100\begin{array}{ccc}_{<<2}&11001101\\\hline&001101\color{red}00\end{array}

可以看到,左边移出的数据直接舍弃,右边不足的用 0 补齐。

右移

右移运算符(>>)和左移同理。

比如:

0b11001101 >> 2;//右移2位

>>21100110100110011\begin{array}{ccc}_{>>2}&11001101\\\hline&\color{red}00\color{black}110011\end{array}

实际应用

由于二进制只有 0 和 1 两个数字,正好对应电路的“通”和“断”两种状态。因此在工控或家电领域使用十分广泛。而配合位操作则可以实现电路控制。不过你是没办法对其中的一个位进行单独的控制的,因此,在这之前,我们要先了解下掩码

掩码

掩码就是一串数字,通过和目标数字的按位操作,来实现对一个或多个位的操作,但不影响其它位。

比如一个 8 核的CPU,我们要通过掩码来控制核心的开关:

1234567800000000\begin{array}{ccc}&核心1&核心2&核心3&核心4&核心5&核心6&核心7&核心8\\\hline&0&0&0&0&0&0&0&0\end{array}

现在要打开核心4,但是不能直接控制第 4 个 0,那就需要通过掩码来解决:

12345678000000000001000000010000\begin{array}{ccc}&核心1&核心2&核心3&核心4&核心5&核心6&核心7&核心8\\\hline&0&0&0&0&0&0&0&0\\_{按位或}&0&0&0&1&0&0&0&0\\\hline&0&0&0&1&0&0&0&0\end{array}

按位或运算时用的 00010000 就是我们说的掩码。

关闭位

假设我们现在想要关闭核心 4,那我们可以这样:

1234567800010000\begin{array}{ccc}&核心1&核心2&核心3&核心4&核心5&核心6&核心7&核心8\\\hline&0&0&0&1&0&0&0&0\end{array}

0001000011101111\begin{array}{ccc}掩码\\_{按位取反}&00010000\\\hline&11101111\end{array}

然后将取反的掩码:

12345678000100001110111100000000\begin{array}{ccc}&核心1&核心2&核心3&核心4&核心5&核心6&核心7&核心8\\\hline&0&0&0&1&0&0&0&0\\_{按位与}&1&1&1&0&1&1&1&1\\\hline&0&0&0&0&0&0&0&0\end{array}

有人可能会问了,为啥要多此一举来取反呢?直接将掩码设置为 11101111不就可以了吗?

这里其实是有个规定,就是掩码中的 1 通常都是对应被控制的那一位。

配合位操作,可以实现很多的玩法,这里就不一一列举了,自己可以去探索一下。