4.7 按位操作符
几乎所有编程语言都提供了一套按位操作符来处理值的二进制形式。
初学者主题:位和字节
计算机的所有值都表示成1和0的二进制形式,这些1和0称为二进制位(bit)。8位一组称为字节(byte)。一个字节中每个连续的位都对应2的一个乘幂。其中,最右边的位对应20,最左边的对应27,如图4.1所示。
图4.1 对应的占位值
在许多情况下,尤其是在操作低级设备或系统服务的时候,信息是以二进制数据的形式获取的。操作这些设备和服务需要处理二进制数据。
如图4.2所示,每个框都对应2的某个乘幂。字节(8位构成的一个数)的值是含有1的所有位的2的乘幂之和。
图4.2 计算无符号字节的值
对于有符号的数,二进制转换则有很大的不同。有符号的数(long、short、int)使用2的补数记数法表示。所以将负数加到正数上时,加法运算可以照常进行,就好像两个数都是正数。使用这种记数法,负数在行为上有别于正数。负数通过最左侧的1来标识。如果最左边的位置包含1,就要将含有0的位置加到一起,而不是将含有1的位置加到一起。每个位置都对应负的“2的乘幂”。此外结果还要减1。图4.3对此进行了演示。
图4.3 计算有符号字节的值
所以,1111 1111 1111 1111对应-1,1111 1111 1111 1001对应-7,而1000 0000 0000 0000对应16位整数能容纳的最小负值。
4.7.1 移位操作符
有时要将一个数的二进制值向右或向左移位。左移时,所有位都向左移动由操作符右侧的操作数指定的位数。移位后在右边留下的空位由零填充。右移位操作符原理相似,只是朝相反方向移位;但如果是负数,左侧填充1而非0。两个移位操作符是>>和<<,分别称为右移位和左移位操作符。除此之外,还有复合移位和赋值操作符<<=和>>=。
例如int值-7,其二进制形式为1111 1111 1111 1111 1111 1111 1111 1001。代码清单4.39使其右移2个位置。
代码清单4.39 使用右移位操作符
输出4.17展示了代码清单4.39的结果。
输出4.17
右移位时最右侧的位在边界处“离开”,左边的负数位标识符右移两个位置,腾出来的空白位置用1填充。最终结果是-2。
虽然传说x << 2比x*4快,但不要将移位操作符用于乘除法。20世纪70年代的一些C编译器可能确实如此,但现代微处理器都对算术运算进行了完美的优化。通过移位进行乘除令人迷惑,而且假如维护代码的人忘记移位操作符的优先级低于算术操作符,还很容易造成错误。
4.7.2 按位操作符
有时需要对两个操作数执行逐位的逻辑运算,比如AND、OR和XOR等,这分别是用&、|和^操作符来实现的。
初学者主题:理解逻辑操作符
假定有如图4.4所示的两个数,按位操作符从最左边的位开始逐位进行逻辑运算,直到最右边的位为止。值1被视为true,值0被视为false。
图4.4 12和7的二进制形式
所以,对图4.4的两个值执行按位AND运算,会逐位比较第一个操作数(12)和第二个操作数(7),得到二进制值000000100,也就是十进制4。另外,这两个值的按位OR运算结果是00001111,也就是十进制15。XOR结果是00001011,也就是十进制11。
代码清单4.40演示了如何使用这些按位操作符,结果如输出4.18所示。
代码清单4.40 使用按位操作符
输出4.18
在代码清单4.40中,值7称为掩码,作用是通过特定的操作符表达式,公开(expose)或消除(eliminate)第一个操作数中特定的位。注意和AND (&&)操作符不同,&操作符总是两边求值,即使左边为false。类似地,OR操作符的|版本也不进行“短路求值”。即使左边的操作数为true,右边也要求值。总之,AND和OR操作符的按位版本不进行“短路求值”。
将一个数转换成二进制需迭代它的每一位。代码清单4.41展示了如何将整数转换成二进制形式的字符串,输出4.19展示了结果。
代码清单4.41 获取二进制形式的字符串表示
输出4.19
注意,每次for循环(稍后就会讨论)都使用右移位赋值操作符创建与变量value中的每一位对应的掩码。使用按位操作符&,可判断一个特定的位是否已设置(是否设为1)。如掩码测试生成非零结果,就将1写到控制台,否则写0。这样可真实反映一个无符号长整数的二进制形式。
注意(mask & value) !=0中的圆括号是必需的,因为不相等测试的优先级高于AND操作符。不显式添加圆括号就相当于mask & (value !=0)。这没有任何意义,&左侧是一个ulong,右侧是一个bool。
本例仅供参考,有内建的CLR方法System.Convert.ToString(value, 2)可直接执行这个转换。第二个参数指定进制(2代表二进制,10代表十进制,16代表十六进制)。
4.7.3 按位复合赋值操作符
按位操作符也可以和赋值操作符合并,即&=、|=和^=。例如,可让变量与一个数进行OR运算,结果赋回初始变量,代码清单4.42进行了演示。
代码清单4.42 使用逻辑赋值操作符
输出4.20展示了结果。
输出4.20
使用fields &=mask这样的表达式将位映射与掩码合并,会从fields中消除mask中没有设置的位。相反,fields &=~mask从fields中消除mask中已设置的位。
4.7.4 按位取反操作符
按位取反操作符反转操作数的每一位,操作数可以是int、uint、long和ulong类型。例如,~1返回1111 1111 1111 1111 1111 1111 1111 1110,而~(1<<31)返回0111 1111 1111 1111 1111 1111 1111 1111。