谈异或运算^和他的常用作用。
异或的运算方法是二进制运算。
1^1=0
0^0=0
1^0=1
0^1=1
两者相等是0,不相等的是1。
这样,您就发现在交换两个整数的值时可以不使用第三个参数。
例如a=11,b=9.以下为二进制
a=a^b=1011^1001=0010;
b=b^a=1001^0010=1011;
a=a^b=0010^1011=1001;
这样的话,a=9,b=13。
列举一个运用,一个按钮就可以交换两个mc的位置。
mybt.onPress=function (
{
mc1._x=mc1._x^mc2._x;
mc2._x=mc2._x^mc1._x;
mc1._x=mc1._x^mc2._x;
//
mc1._y=mc1._y^mc2._y;
mc2._y=mc2._y^mc1._y;
mc1._y=mc1._y^mc2._y;
}
这样,就可以不通过监狱变量进行传递。
最后声明。 只能在整数中使用。
1 .位运算请评估整数在计算机中用二进制位表示。 c语言具有一个运算符,它可以直接操纵整数位(称为位运算),这些运算符的所有操作数都必须是整数。 在今后的学习中,您将发现一些信息是利用整数中的某些位存储的。 要访问这些位,仅有对整数的操作是不够的,还需要利用位运算。 例如,第2节“Unicode和UTF-8”中介绍的UTF-8编码就是这样。 学习了本节之后,你应该能自己编写UTF-8的编码和解码程序。 本节首先介绍各种位运算符,然后介绍与位运算相关的编程技巧。
1.1 )关于位和、或、异或、逆运算,请在第3节“布尔代数”中对逻辑和、或非运算进行说明,并排列真值表。 整数中的位也可以进行逻辑和或非运算。 C语言提供位和(Bitwise AND )运算符、位和(Bitwise OR ),以二进制格式提供一些示例。
图16.1.位运算
请注意,|,^运算符都必须使用Usual Arithmetic Conversion。 (其中的一步是Integer Promotion。 )因为运算符也必须Integer Promotion,所以c语言中实际上不存在8位整数的位运算,且操作数在位运算之前必须至少具有例如:
unsigned char c=0xfc;
unsigned int i=~c; 计算过程如下。 常量0xfc是int类型,它将转换为c的unsigned char,并且其值保持不变。 的十六进制表示法为fc,计算~c时先升到整数型(000000fc )再取反,最终成为ffffff03。 请注意,如果将~c视为8位整数的反取,则最后的结果为3。 这是错误的。 为了避免错误,一个是尽量避免不同类型之间的代入,另一个是每次计算时根据上一章所述的类型转换规则进行仔细检查。
1.2 .移位运算请评估移位运算符(Bitwise Shift )包含左移和右移。 左移将整数的每个二进制位全部左移几个位。 例如,0xcfffffff32将获得0x3fffffcc。
图16.2.左移运算
最上面的两个人的11被取下,最下面的两个人又增加了两个0,其他两个人依次向左移动了两个。 但是,请注意,要移动的位数必须小于左操作数的总位数。 例如,在上面的示例中,如果左边是unsigned int类型,并且左移的位数大于或等于32位,则结果为Undefined。 移位运算符与- */==等运算符不同。 两边操作数类型不必匹配,但所有两边操作数都执行Integer Promotion。 整个表达式的类型与左操作数被提升的类型相同。
复习第2节“不同进制之间的换算”中所述的知识,可以得出结论:在一定的取值范围内,将整数左移一位相当于乘以2。 例如,二进制11 (十进制3 )左移一位,到110时为6,再左移一位,到1100时为12。 读者可自行验证该规律对有符号数和无符号数均成立,对负数也成立。 当然,左移改变最上位(符号位)时,结果并不一定会变成2倍,所以以“在一定的可取范围内”为前提。 由于计算机进行移位比乘法运算快得多,编译器可以利用它进行优化。 例如,您可以看到源代码中有i * 8,然后将它编译成班次
位指令而不是乘法指令。
当操作数是无符号数时,右移运算的规则和左移类似,例如0xcfffffff3>>2得到0x33fffffc:
图 16.3. 右移运算
最低两位的11被移出去了,最高两位又补了两个0,其它位依次右移两位。和左移类似,移动的位数也必须小于左操作数的总位数,否则结果是Undefined。在一定的取值范围内,将一个整数右移1位相当于除以2,小数部分截掉。
当操作数是有符号数时,右移运算的规则比较复杂:
如果是正数,那么高位移入0
如果是负数,那么高位移入1还是0不一定,这是Implementation-defined的。对于x86平台的gcc 编译器,最高位移入1,也就是仍保持负数的符号位,这种处理方式对负数仍然保持了“右移1位相当于除以2 ”的性质。
综上所述,由于类型转换和移位等问题,用有符号数做位运算是很不方便的,所以,建议只对无符号数做位运算,以减少出错的可能 。
习题 请点评
1、下面两行printf 打印的结果有何不同?请读者比较分析一下。%x 转换说明的含义详见第 2.9 节 “格式化I/O函数” 。
int i = 0xcffffff3;
printf(“%x/n”, 0xcffffff3>>2);
printf(“%x/n”, i>>2); 1.3. 掩码 请点评
如果要对一个整数中的某些位进行操作,怎样表示这些位在整数中的位置呢?可以用掩码(Mask)来表示。比如掩码0x0000ff00表示对一个32位整数的8~15位进行操作,举例如下。
1、取出8~15位。
unsigned int a, b, mask = 0x0000ff00;
a = 0x12345678;
b = (a & mask) >> 8; /* 0x00000056 */
这样也可以达到同样的效果:
b = (a >> 8) & ~(~0U << 8);
2、将8~15位清0。
unsigned int a, b, mask = 0x0000ff00;
a = 0x12345678;
b = a & ~mask; /* 0x12340078 */
3、将8~15位置1。
unsigned int a, b, mask = 0x0000ff00;
a = 0x12345678;
b = a | mask; /* 0x1234ff78 */ 习题 请点评
1、统计一个无符号整数的二进制表示中1的个数,函数原型是int countbit(unsigned int x); 。
2、用位操作实现无符号整数的乘法运算,函数原型是unsigned int multiply(unsigned int x, unsigned int y); 。例如:(11011)2 ×(10010)2 =((11011)2 <<1)+((11011)2 <<4)。
3、对一个32位无符号整数做循环右移,函数原型是unsigned int rotate_right(unsigned int x, int n); 。所谓循环右移就是把低位移出去的部分再补到高位上去,例如rotate_right(0xdeadbeef, 8) 的值应该是0xefdeadbe。
1.4. 异或运算的一些特性 请点评
1、一个数和自己做异或的结果是0。如果需要一个常数0,x86平台的编译器可能会生成这样的指令:xorl %eax, %eax 。不管eax 寄存器里的值原来是多少,做异或运算都能得到0,这条指令比同样效果的movl $0, %eax 指令快,直接对寄存器做位运算比生成一个立即数再传送到寄存器要快一些。
2、从异或的真值表可以看出,不管是0还是1,和0做异或保持原值不变,和1做异或得到原值的相反值。可以利用这个特性配合掩码实现某些位的翻转,例如:
unsigned int a, b, mask = 1U << 6;
a = 0x12345678;
b = a ^ mask; /* flip the 6th bit */
3、如果a1 ^ a2 ^ a3 ^ … ^ an 的结果是1,则表示a1 、a2 、a3 …an 之中1的个数为奇数个,否则为偶数个。这条性质可用于奇偶校验(Parity Check),比如在串口通信过程中,每个字节的数据都计算一个校验位,数据和校验位一起发送出去,这样接收方可以根据校验位粗略地判断接收到的数据是否有误。
4、x ^ x ^ y == y,因为x ^ x == 0,0 ^ y == y。这个性质有什么用呢?我们来看这样一个问题:交换两个变量的值,不得借助额外的存储空间,所以就不能采用temp = a; a = b; b = temp; 的办法了。利用位运算可以这样做交换:
a = a ^ b;
b = b ^ a;
a = a ^ b;
分析一下这个过程。为了避免混淆,把a和b的初值分别记为a0 和b0 。第一行,a = a0 ^ b0 ;第二行,把a的新值代入,得到b = b0 ^ a0 ^ b0 ,等号右边的b0 相当于上面公式中的x,a0 相当于y,所以结果为a0 ;第三行,把a和b的新值代入,得到a = a0 ^ b0 ^ a0 ,结果为b0 。注意这个过程不能把同一个变量自己跟自己交换,而利用中间变量temp 则可以交换。
习题 请点评
1、请在网上查找有关RAID(Redundant Array of Independent Disks,独立磁盘冗余阵列)的资料,理解其实现原理,其实就是利用了本节的性质3和4。
2、交换两个变量的值,不得借助额外的存储空间,除了本节讲的方法之外你还能想出什么方法?本节讲的方法不能把同一个变量自己跟自己交换,你的方法有没有什么局限性?