位操作
C语言常用于底层开发,它可以与硬件通信并且可以嵌入汇编语言,因此经常需要进行位操作,例如一台IBM PC通过向端口发送指令来控制硬件,控制代码通过读取指令字节上某个位来打开设备,其它位可能储存发送的信息。这就需要提取位上的信息。
进制
日常生活中我们常常使用十进制,原因可能得益于我们有10个手指头和脚趾头,计算机则使用二进制,因为信号的打开和关闭只有两种状态,很容易实现。八进制常用于小型设备或者古老的计算机,现在已经很少使用,目前常用的是十六进制,因为十六进制用F对应二进制1111,FF对应11111111,刚好一个字节,因此用十六进制描述二进制非常方便。用不同进制表述浮点时会有区别,例如1/3不能用十进制精确描述但三进制可以,二进制很擅长描述1/2,1/4,1/8这种2的n次幂,但不能精确表示2/5,3/7,因此当用double进行计算时常常可以看到1/10的输出结果为0.9999999999999999,但只要精度足够也可以用于日常和商业。
位运算符
位运算符分为逻辑运算符和位移运算符,逻辑运算符优先级低于关系运算符和赋值运算符,位移运算符优先级低于加减运算符高于关系运算符,但&=, >>=等赋值运算符优先级与++,+=相同。单纯的逻辑运算不会改变变量的值,例如flags<<2不会改变flags的值,它只返回运算后的结果,只有<<=才会改变flags的值。
位逻辑运算符
-
位逻辑运算符有:
-
按位非
& 按位与
| 按位或
^ 按位异或
&= 按位与后赋值
|= 按位或后赋值
^= 按位异或后赋值
^和&逻辑相反,两个值不同为真,两个相同为假,也可以用两个数相加得到结果,即0^0=0,0^1=1,1^0=1,1^1=0
。
这些逻辑都是从生活现象中总结出来的,对于异或逻辑,例如结婚两人性别必须是异性,同性不允许结婚。位逻辑在数字电路中应用更加广泛,还有一门学科叫数字逻辑,是电子专业必修课之一。在计算机中位运算用法大体如下:
掩码
使用&将一个二进制值的其它位隐藏起来,只保留指定的位,例如mask位00000010,flags & mask的效果如下:
在设置网卡ip的窗口中,掩码常常为255.255.255.0,即将ip地址的低位隐藏起来,只保留高位,如图:
打开和关闭位
将二进制某个位打开可以使用|,假设flags是00001111,mask是10000000,flags | mask结果为:10001111,最高位1被打开其它位不变。将二进制某个位关闭可以使用flags & ~mask,假设mask为00000001,结果为00001110,最低位被关闭。只需使用不同的运算符,同一个mask既可以用来打开又可以用来关闭位。
切换位
打开已关闭的位或者关闭已打开的位,可以使用^切换,因为1与任何位异或都会取反,0与任何位异或结果不变,如下:
1^1=0
1^0=1
0^0=0
0^1=1
如果flash为00000000,mask为11000000,那么flashs ^ mask的结果是11000000,最高两位被取反,其它位不变。
检查位
如果我们想比较两个字节中的位,不能使用flags1flags2这样的方式,因为比较的是字节的整体值,比较位应使用(flags1 & mask)(flags2 & mask)的形式,mask为某个位的掩码,由于&优先级低于赋值因此要用括号括起来。
位移运算符
位移运算符包含:
<< 按位左移
>> 按位右移
<<+ 按位左移后赋值
>>+ 按位右移后赋值
由于基本数据类型最小是1字节,当我们需要存取位数据时常常需要进行左移或右移,例如颜色值通常用三个字节表示,如FF00FF,当我们需要获取红色时需要使用flags>>=4将红色取出。用左移一位相当于将值乘以2,右移一位相当于将值除以2,但是右移与系统有关,因为涉及到负数,有些系统使用0填充空出的位,有些系统使用1填充空出的位,现在的编译器比较智能,如果发现值为正则用0填充,为符则用1填充,测试代码如下:
#include<stdio.h>
#include<stdio.h>
int main(void)
{
unsigned a = 0x000040;
int b = 0x000040;
printf("%d,%d,%d,%d",a>>1,b>>1,4>>1,-4>>1);
return 0;
}#include<stdio.h>