编程基础:位运算03,按位与运算
专栏导航
本节文章分别属于《Win32 学习笔记》和《MFC 学习笔记》两个专栏,故划分为两个专栏导航。读者可以自行选择前往哪个专栏。
(一)WIn32 专栏导航
上一篇:编程基础:位运算02,按位取反
下一篇:编程基础:位运算04,按位或运算
(二)MFC 专栏导航
上一篇:编程基础:位运算02,按位取反
下一篇:编程基础:位运算04,按位或运算
本节前言
在上一节,我们讲解了位运算中的按位取反运算。
在本节,我们来讲解按位与运算。
在讲解本节的时候,我会用到上一节所讲的按位取反的知识。所以呢,如果你还不会按位取反的知识,请你先去本专栏的上一节,先学好按位取反的知识。
本节会用到许多的进制知识,比如说十进制数与二进制数的相互转换,二进制数与十六进制数的相互转换。下面,我来列出参考链接。
计算机基础:二进制基础04,十进整制数转化为二进制整数-CSDN博客
计算机基础:二进制基础13,十六进制与二进制的相互转换-CSDN博客
这几个链接都是本专栏里面的文章。如果有列举不到的地方,大家可以亲自前往专栏目录来查看信息。
Win32 目路和 MFC 目录,都可以查看到相应的信息。
我们开始。
一. 位运算的位
学习位运算知识的同学,需要首先注意一点。这里的位,仅仅是指二进制位,它不是指十进制位,不是指八进制和十六进制位,仅仅是指二进制位。
在计算的时候,如果是说,我们自己来手算,那么,我们需要先将不同的操作数,转换为二进制数,然后让不同的二进制操作数来运算,然后得出结果。
在程序中,我们不需要将其转换为二进制数,因为,不论是说,我们在程序中用的是十进制数,还是八进制,十六进制数,在计算机里面,都是用二进制来表示的。至于说转换为相同的二进制数的位数的过程,这个是由编译器完成的,我们不需要操心。
我之所以谈到了手算的过程,这只是为了让大家理解原理。
二. 按位与运算,1 位
按位与的运算符为【&】,它与逻辑与的符号比较相似。逻辑与的符号为【&&】,而按位与的符号,不是两个,而是一个【&】。
我们先来看两个 1 位二进制数的按位与的运算情况,我们用下方的表格来演示这种情形。
| 操作数1 | 操作数2 | 与运算结果 |
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
从表格中,我们看到,单独的 1 位的两个二进制数进行按位与运算的时候,只有当两个数都是 1 的时候,运算结果才为 1。只要有一个数是 0,则按位与运算的结果就是 0 。这与逻辑与的运算法则是相似的。
对于 1 位 的两个操作数的按位与运算,我们可以将其类比乘法。
0 & 0,有点类似于 0 × 0,结果都是 0 。
0 & 1,有点类似于 0 × 1,结果都是 0 。
1 & 0,有点类似于 1 × 0,结果都是 0 。
1 & 1,有点类似于 1 × 1,结果都是 1 。
三. 按位与运算,多位
两个多位二进制数的按位与运算,它是将两个二进制数的相同的数位上的数,分别进行与运算。
比如说,两个 unsigned short 类型的操作数进行按位与运算,每一个操作数都是 16 位的。
两个操作数,位 0 和 位 0 相与,位 1 和 位 1 相与,位 12 和 位 12 相与,位 15 和 位 15 相与。
假定两个操作数的数据类型都是 unsigned short,变量名分别为 num01 和 num02 。我们让这两个操作数进行按位与运算,并将运算结果放在 unsigned short 类型的 变量 res 里面。
运算的规则如下。
res 的 位 0,等于 【num01 的 位 0 & num02 的位 0】。
res 的 位 1,等于 【num01 的 位 1 & num02 的位 1】。
res 的 位 3,等于 【num01 的 位 3 & num02 的位 3】。
res 的 位 11,等于 【num01 的 位 11 & num02 的位 11】。
res 的 位 15,等于 【num01 的 位 15 & num02 的位 15】。
总之,结果变量的某一位,等于两个操作数的对应位的与运算的结果。
现在,我们给出下面的代码。
unsigned char n1, n2;
unsigned char res;
n1 = 11;
n2 = 7;
res = n1 & n2;
在这里,我们是手算,所以,我们先将两个操作数转换为二进制数。两个数都是 8 位的,所以转换为 8 位二进制数即可。
十进制的 11,转换为二进制数,结果为 0000 1011 。
十进制的 7,转换为二进制数,结果为 0000 0111 。
我们在表格中来演示这两个数的运算。
| 数位 | 位7 | 位6 | 位5 | 位4 | 位3 | 位2 | 位1 | 位0 |
| 操作数1 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 1 |
| 操作数2 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 |
| 运算结果 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
在上面的表格里面,两个数的高 4 位,由于都是 0,所以,运算结果中的高 4 位也都是 0 。我们重点看两个数的低 4 位。
对于两个数的位 3,操作数1 的 位 3 为 1,操作数 2 的位 3 为 0,与运算的结果为 0,运算结果一行的位 3 为 0 。
对于两个数的位 2,操作数1 的 位 2 为 0,操作数 2 的位 2 为 1,与运算的结果为 0,运算结果一行的位 2 为 0 。
对于两个数的位 1,操作数1 的 位 1 为 1,操作数 2 的位 1 为 1,与运算的结果为 1,运算结果一行的位 1 为 1 。
对于两个数的位 0,操作数1 的 位 0 为 1,操作数 2 的位 0 为 1,与运算的结果为 1,运算结果一行的位 0 为 1 。
按位与运算的一个要点是,它是两个操作数的相同的二进制数位进行与运算,并将运算结果放在结果变量的相同的二进制位上面。两个数的所有的二进制数位都进行了与结算,将所有位的与结算结果连缀起来,就是按位与运算的结果。
以上的表述,对你来讲,可能显得有些麻烦和啰嗦。个人能力问题,表达能力差。
到了这里,按位与运算的运算规则,我们算是讲完了它的基本的内容了。
然而,在实际编程的时候,我们往往不是使用按位与运算的基本内容,而是使用它的推论用法。怎么个推论用法呢?我们在下面的两个分节中予以阐述。
四. 按位与运算的一个规律
我们先将第二分节的表格粘贴到下面。
| 操作数1 | 操作数2 | 与运算结果 |
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
从 表格可以看到,当某一个 1 位的二进制数 和 1 位的 0 进行按位与运算的时候,不论这个数是 0 还是 1,则按位与运算的结果都是 0 。我们将其记为,同 0 相与,此位清零。
当大家学习汇编语言,或者学习 Linux内核,有可能会接触到【清零】这一术语的其他的叫法。
将某一个二进制位设置为 0,可以叫做【清零】,也可以叫做【清除】。
从 表格可以看到,当某一个 1 位的二进制数 和 1 位的 1 进行按位与运算的时候,如果这个数是 0,则运算结果为 0 。如果这个数是 1,则按位与运算的结果是 1 。也就是说,当某一个 1 位的二进制数同 1 相 与的时候,这个数是什么,运算结果就是什么。所以呢,我们将其记为,同 1 相与,此位保留。
我们将上面的两种简记列在下面。
同 0 相与,此位清零。同 1 相与,此位保留。
进一步地,我们将按位与运算的法则给简化一下,结果为:0 位清零,1 位保留。
所以,按位与运算的一个规律是,0 位清零,1 位保留。
假定,一个二进制数的值为 1111 0000,它和另一个 8 位二进制数进行按位与运算,则它的意思是,,将另外的那个二进制数的高 4 位保留不变,而将其低 4 位清零。
我们再来看一个代码。
unsigned char n1, n2;
unsigned char res;
n1 = 0x3C;
n2 = 0x5D;
res = n1 & n2;
n1 的值,用十六进制表示,它是 0x3C,转化为 8 位二进制形式,结果为 0011 1100 。
n2 的值,用十六进制表示,它是 0x5D,转化为 8 位二进制形式,结果为 0101 1101 。
转换为二进制形式之后,我们发现,n1 的值比较规则一些,它的高 2 位和低 2 位为 0,而位 5 到位 2 为 1,所以呢,n1 与 n2 进行按位与运算,其运算的思路是,将 n2 的高 2 位和低 2 位清零,而将 n2 的位 5 到位 2 保留不变,结果为 0001 1100,这个值的十六进制值为 0x1C,它的十进制值为 28 。
在本分节里面,大家重点需要去掌握的,就是按位与运算的规律,0 位清零,1 位保留。
我们再来看下一个推论法则。
五. 取反相与
什么叫做取反相与呢?
我们来看看下面的代码片段。
unsigned char n1, n2;
n1 = 0x7B;
n2 = 0x0F;
n1 &= ~n2;
看到了没?类似于【操作数1 &= ~操作数2】的代码形式,便是取反相与。
取反相与,这算是一种常见的位运算。在 Win32,MFC,以及 Linux 内核的学习中,大概,你时不时地,会见到取反相与的情况。
在这里,我们来试着探索一下取反相与的运算法则。
我们先来看代码块中的代码。n2 转换为二进制数,结果为 0000 1111 。
代码块中,要去计算的是 【n1 &= ~n2】,它的等价形式为【n1 = n1 & (~n2)】。
为了模拟执行这个代码,我们需要先将 n2 进行按位取反,结果为 1111 0000 。
然后呢,接下来,我们需要计算【n1 & 1111 0000】。
在这里,我们似乎可以用到第四分节中所讲的按位与运算的规律,0 位清零,1 位保留。
所以,此代码块的意思,将 n1 的高 4 位予以保留不变,将 n1 的低 4 位清零。
n1 的十六进制值为 0x7B,转换为二进制形式,为 0111 1011 。
结合着 n1 的值与【0 位清零,1 位保留】的规律,结合着【~n2】的值 1111 0000,再结合着本代码块的运算意图【将 n1 的高 4 位予以保留不变,将 n1 的低 4 位清零】,本代码块的运算结果为 0111 0000,其十六进制形式为 0x70 。
到这里,代码块的运算,我们就讲完了。可是,取反相与的运算法则我们还没有总结出来。
我们将取反相与表述为这样的形式:目标操作数 &= ~参考操作数。
我们将参考操作数取反之后的结果,暂时称为中间结果。
一般地,中间结果,它的各个二进制位,是有 0 有 1 。
这一中间结果与原始目标操作数进行按位与运算,遵循的规则是【0 位清零,1 位保留】。
原始目标操作数中,对应于中间结果里面为 0 的二进制位,会被清零。
原始目标操作数中,对应于中间结果里面为 1 的二进制位,会被保留。
而中间结果里面为 0 的位,它在参考操作数里面,它的值为 1 。
中间结果里面为 1 的位,它在参考操作数里面,它的值为 0 。
所以呢,取反相与的操作的运算法则是,在参考餐作数里面为 1 的二进制位,原始目标操作数中对应的位会被清零;在参考操作数里面为 0 的二进制位,原始目标操作数中对应的位会被保留。
简化一下,取反相与的运算法则是,0 位保留,1 位清零。
到了这里,取反相与,我们就讲完了。习题,我就不去设置了,你自己随便给自己出几个题目吧。
结束语
按位与运算,是很常见的一种位运算。编程实践中,常常用它来将一个操作数的某一位或某几位清零。取反相与,是一个典型的按位与运算的运算类型。
对于按位与运算的内容,希望大家能够掌握。
专栏导航
本节文章分别属于《Win32 学习笔记》和《MFC 学习笔记》两个专栏,故划分为两个专栏导航。读者可以自行选择前往哪个专栏。
(一)WIn32 专栏导航
上一篇:编程基础:位运算02,按位取反
下一篇:编程基础:位运算04,按位或运算
(二)MFC 专栏导航
上一篇:编程基础:位运算02,按位取反
下一篇:编程基础:位运算04,按位或运算
更多推荐
所有评论(0)