本文源于本人<<Linux c 一站式学习>>总结笔记
(一) 位运算
注意,&、|、^运算符都是要做UsualArithmetic Conversion的(其中有一步是Integer
Promotion),~运算符也要做IntegerPromotion,所以在C语言中其实并不存在8位整数的位运
算,操作数在做位运算之前都至少被提升为int型了,上面用8位整数举例只是为了书写方便。比
如:
#include <stdio.h>
int main(void)
{
unsigned char c =0xfc;
printf("%x\n",c);
unsigned int i = ~c;
printf("%x\n",i);
return 0;
}
gdb调试结果为:
计算过程是这样的:常量0xfc是int型的,赋给c要转成unsignedchar ,值不变;c的十六进制表示
是fc,计算~c时先提升为整型(000000fc)然后取反,最后结果是ffffff03。注意,如果把~c看成
是8位整数的取反,最后结果就得3了,这就错了。为了避免出错,一要尽量避免不同类型之间的赋
值,二是每一步计算都要按类型转换规则仔细检查。
移位操作:
移位运算符(BitwiseShift)包括左移<<和右移>>。左移将一个整数的各二进制位全部左移若干
位,例如0xcfffffff3<<2得到0x3fffffcc:
最高两位的11被移出去了,最低两位又补了两个0,其它位依次左移两位。但要注意,移动的位数
必须小于左操作数的总位数,比如上面的例子,左边是unsignedint型,如果左移的位数大于等
于32位,则结果是Undefined。移位运算符不同于+- * / ==等运算符,两边操作数的类型不要求一
致,但两边操作数都要做IntegerPromotion,整个表达式的类型和左操作数提升后的类型相同。
在一定的取值范围内,将一个整数左移1位相当于乘以2,因为如果左移改变了最高位(符号位),那么结果肯定不是乘以2了,这条规律对有符号数和无符号数均成立。
当操作数是无符号数时,右移运算的规则和左移类似,例如0xcfffffff3>>2得到0x33fffffc:
最低两位的11被移出去了,最高两位又补了两个0,其它位依次右移两位。和左移类似,移动的位
数也必须小于左操作数的总位数,否则结果是Undefined。在一定的取值范围内,将一个整数右
移1位相当于除以2,小数部分截掉。
当操作数是有符号数时,右移运算的规则比较复杂:
如果是正数,那么高位移入0
如果是负数,那么高位移入1还是0不一定,这是Implementation-defined的。对于x86平台
的gcc编译器,最高位移入1,也就是仍保持负数的符号位,这种处理方式对负数仍然保持
了“右移1位相当于除以2”的性质。
由于类型转换和移位等问题,有符号数做位运算是很不方便的,建议只对无符号数做位运算,以减少出错的可能。
(二)掩码
如果要对一个整数中的某些位进行操作,怎样表示这些位在整数中的位置呢?可以用掩码
(Mask)来表示。比如掩码0x0000ff00表示对一个32位整数的8~15位进行操作,举例如下。
#include <stdio.h>
int main(void)
{
unsigned int a, b, mask = 0x0000ff00;
/*取出8~15位*/
a = 0x12345678;
b = (a & mask) >> 8;
printf("%x\n",a);
printf("%x\n\n",b);
/*将8~15位清0*/
a = 0x12345678;
b = a & ~mask;
printf("%x\n",a);
printf("%x\n\n",b);
/*将8~15位置1*/
a = 0x12345678;
b = a | mask;
printf("%x\n",a);
printf("%x\n\n",b);
return 0;
}
习题:
1、统计一个无符号整数的二进制表示中1的个数,函数原型是intcountbit(unsigned int x);
#include<stdio.h>
int countbit(unsignedint x)
{
int count;
for(count=0; x>0;count++)
x&=x-1; //把最后面的1变0
return count;
}
int main(void)
{
unsigned int a;
printf("Pleaseinput an integer: ");
scanf("%i",&a);
printf("%d\n",countbit(a));
return 0;
}
2、用位操作实现无符号整数的乘法运算,函数原型是unsignedint multiply(unsigned int x,
unsigned inty);。例如:(11011)2×(10010)2=((11011) 2 <<1)+((11011)2 <<4)。
#include<stdio.h>
unsigned intmultiply(unsigned int x, unsigned int y)
{
int ans = 0;
while (x > 0)
{
if (x & 1)
{
ans += y;
}
y <<= 1;
x >>= 1;
}
return ans;
}
int main(void)
{
unsigned int x = 123;
unsigned int y = 456;
printf("%d\n",multiply(x, y));
return 0;
}
3、对一个32位无符号整数做循环右移,函数原型是unsignedint rotate_right(unsigned int
x);。所谓循环右移就是把低位移出去的部分再补到高位上去,例如rotate_right(0xdeadbeef,
16)的值应该是0xefdeadbe。
#include <stdio.h>
unsigned introtate_right(unsigned int x, int n)
{
int save;
int i;
for(i=0;i<n;i++)
{
save=x&0x00000001;
x=x>>1;
save=save<<7;
x+=save;
}
return x;
}
int main(void)
{
unsigned int x = 123;
printf("%d\n",rotate_right(x,32));
return 0;
}
4、交换两个变量的值,不得借助额外的存储空间,
#include<stdio.h>
void swap1(unsigned intx, unsigned int y)
{
x = x ^ y;
y = y ^ x;
x = y ^ x;
printf("Swap1:");
printf("%d,%d\n", x, y);
}
void swap2(unsigned intx, unsigned int y)
{
x = x + y;
y = x - y;
x = x - y;
printf("Swap2:");
printf("%d,%d\n", x, y);
}
int main(void)
{
unsigned int a = 123;
unsigned int b = 456;
printf("Original:");
printf("%d,%d\n", a, b);
swap1(a,b);
swap2(a,b);
return 0;
}