自学内容网 自学内容网

C语言基础:掩码运算(位运算)

前言

        以前写过一篇流水灯的帖子单片机C51之1:流水灯_小灯连续亮灭程序是什么-CSDN博客,里面用到了位运算,当时写代码的思路还不是很清晰,对位运算用得不是很熟练,笔者最近在看的<深入理解计算机系统>(以下称"本书")里也提到了位运算,所以对此做一个较为完整的用法的归纳

引入

        C语言和C++是偏向底层的语言,用起来肯定不如Java之类"纯面向对象"的语言那么舒服,但也正是因为其偏底层,对喜欢探索计算机底层的人来说,会比较有趣.

移位运算基础

        数据在内存中以字节为单位存在.先简单理解下移位运算的规则:

        假设有一个数据:int a=0B10101101;       

        注:二进制比较容易说明,相当于十六进制的0xad;十进制的173.

        1>数据左移:移出位丢弃,补0;

        a=a<<4;                    //左移4位

        结果:0B11010000;   

#include<stdlib.h>
#include<stdio.h>

int main(void) {
unsigned char a = 0B10101101;//无符号八位整数
a=a << 4;//左移4位
printf("%d\n", a);            //a值等于0B11010000
printf("%d", 0B11010000);//打印结果同上
}

        因为在C语言中,没有专门的二进制数据类型,所以用了unsigned char(0~127)这个类型表示八位二进制数0B10101101,左移四位结果是没有问题的.如果用int或者其他整型接收原始数据,得出的结果虽然用十进制表示不一样,但移位的结果是相同的,可以验证

        2>数据右移,移出位丢弃

        本书上说大部分编译器都是补最左端数,本例中为1,即结果应为0B11111101但vs2019打印出来是补0.

a = a >> 4;//右移4位
printf("%d\n", a);//打印成十进制
printf("0x%x\n", a);//打印成十六进制

        结果是13,由上一步的a值0B11010000移位得来,运算后的a值是0B00001101(十六进制0xd),如果在某个编程环境下打印出来结果是0B11111101,十进制253,十六进制0xfd,也不用奇怪,那是右移补最左端数的规则

        关于这一点不用纠结,因为在移位运算中只用左移就可以了,不用担心出错.

置位运算

        置位简单来说就是设为"1"(true),为何要叫做运算呢?因为他能适应更多的情况,让代码变得简单.用一种"移位或"的方法来置位.

        举例:假设有一个数据0B11000000,要将其第4位设为1.写法如下:

unsigned char b = 0B11000000;
b = b |1<< 4;                       //移位或
printf("%d\n", b);//结果208,二进制0B11010000,移位成功

        不论原始数据第4位是否为1,经移位或运算以后,已被设置为1.

        注意:移位运算是从第0位算起,所以移4位,从左数起是第5个数字.

复位运算

        复位运算与置位运算相反,是设为"0",用一种"移位与"(移位取反与)的方法实现.

        假设将前面的b(0B11010000)第4位复位为0,写法如下:

unsigned char tmp = 1 << 4;//复位运算,建立临时数据tmp;
b = b & ~tmp;//取反与
printf("%d\n", b);//结果192,二进制0B11000000

        复位的思路:将某个位复位为0(比如第n个)→和这个数据(b)相与:0B111110111(数据中的0处于第n位)→b的计算:先移位或----1<<n再将其取反.

        复位用了一个位运算的特点:和1相与,原值不变;同样置位运算也用了一个特点:和0相或,原值不变.所以上述代码有个好处是:除了修改目标位,其他位不受影响.

多个位的置位和复位

        上面的例子演示了把第4位置位和复位,如果一次要多个置位或复位呢?也比较简单,只要在代码上用相同的方法添加就行了.比如同时置位和复位第4位和第5位

        置位4,5两个位

unsigned char b = 0B11000000;
b = b |1<< 4;                       //移位或第4位
    b = b |1<< 5;                       //移位或第5位
printf("%d\n", b);

        复位4,6两个位

unsigned char tmp = 1 << 4;//复位运算,建立临时数据tmp;
    tmp = tmp | 1 << 5;                 //移位或第5位
b = b & ~tmp;//取反与
printf("%d\n", b);

==========================内容分割线=======================================

        有必要说一下可能的疑问:为什么推荐用运算的方式去设置位数据,可以"硬写"啊,当某情况出现的时候,把寄存器的值直接修改不就行了?是的,没错,直接改也是可以的.但考虑到如果寄存器不是8位的,如32位的,现在给你一张32位的表,注明了每个位在0和1时分别代表什么意思,用来写代码

        32位寄存器用十六进制表示:0x1234568(示例),如果是二进制数0B1111 1111 1111 1111 1111 1111 1111 1111,硬写时要么把目标数据换算成十六进制,要么需要数多少个0,位数多了容易出错.更不要说现在用64位编译器了.

==========================内容分割线====================================== 

切换位 

        好像还有个运算符^(异或)还没派上用场.只要是想用他,一定会用得着.

        先看位计算的一个特点:.

        0^0=0; 1^0=1;               //      和0异或仍为原值   

        0^1=1;  1^1=0;              //       和1异或切换原值 

        切换位的思路:切换(比如第n位)位→原始数据与这样的一个数(如tmp)进行异或运算:第n位为1,其他不需要切换的位为0→移位或做出tmp→反向写出代码

        举例:把0B11000000中第4和第5位切换,代码如下:

/*切换位运算*/
unsigned char tmp2 = 1 << 4;//切换位运算,建立临时数据tmp2;
tmp2 = tmp2 | 1 << 5;
tmp2 = tmp2 | 1 << 7;
b = b ^ tmp2;
printf("%d\n", b);//结果112,二进制0B01110000

小结 

        位运算的理解和应用.实现了置位,复位,切换位的功能.

         C++的写法和C语言在位控制上基本一致.

        个人感受:每天用面向对象考虑问题,偶尔写写C的代码,也算换换思路.

         后面会做一个和控制有关的实例.


原文地址:https://blog.csdn.net/jllws1/article/details/142694098

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!