第15章 位操作 15.3 C的位运算符

C提供位的逻辑运算符和移位运算符。

15.3.1  位的逻辑运算符

4个位运算符用于整型数据,包括char。将这些运算符称为位运算符的原因是它们对每位进行操作,而不影响左右两侧的位。而常规逻辑运算符(&&、||、!)对整个值进行操作。

一、二进制反码或按位取反

一元运算符~将每个1变0,将每个0变1,如下面的例子所示:

~(10011010)  //表达式

(01100101)  //结果

假设val是一个unsigned char,已赋值为2。在二进制中,2是00000010。于是~val的值为11111101,或253。

请注意该运算符不改变val的值,正如3*val不改变val的值一样;val仍为2,但是该运算符并不创建一个可以在别处使用或被赋值的新值。

newval=~val;

printf("%d",~val);

如果您想将val的值变为~val,请使用简单的赋值:

val=~val;

二、位与(AND):&

二进制运算符&通过对两个操作数逐位进行比较产生一个新值。对于每个位,只有两个操作数的对应位都为1时结果才为1。

(10010011)&(00111101)  //表达式

的结果值是:

(00010001)    //结果值

原因是在两个操作数中,只有位4和0都为1。

C也有一个组合的位与-赋值运算符:&=。下面的语句产生相同的最终结果:

val &=0377;

val = val & 0377;

三、位或(OR):|

二进制运算符|通过对两个操作数逐位进行比较产生一个新值。对于每个位,如果其中任意操作数中对应的位为1,那么结果为1。因此:

(10010011) | (00111101)  //表达式

的结果值是:

(10111111)  //结果值

C也有一个组合的位或-赋值运算符:|=。下面两个语句产生相同的最终结果:

val |= 0377;  val = val | 0377;

四、位异或

二进制运算符^对两个操作数逐位进行比较,对于每个位,如果操作数中的对应位有一个为1(但是不都为1),那么结果为1。因此:

(10010011) ^ (00111101)  //表达式

的结果值是:

(10101110)  //结果值

请注意,因为两个操作数中的位0都为1,因此位0的结果为0。

C也有一个组合的位异或-赋值运算符:^=。下面两个语句产生相同的最终结果:

val ^= 0377;

val = val ^ 0377;

15.3.2  用法:掩码

“位与”运算符通常与掩码一起使用。掩码是某些位设为开而某些位设为关的位组合。

例如,假设您定义符号常量MASK为2,即二进制00000010,只有位1是非零。那么:

flags = flags & MASK;

这个语句将导致flags的除位1之外的所有位都被设为0,原因是它的任何位使用&运算符与0组合都得0;位1将保持不变(如果该位为1,则1&1为1;如果该位为0,则0&1为0)。因为掩码中的0覆盖了flags中的相应位,所以该过程称为“使用掩码”。

依此类推,您可以将掩码中的0看作不透明,将1看作透明。表达式flags&MASK就好像使用掩码覆盖flags位组合;flags中的位只有在MASK中对应的位是1时才可见。

可以使用“与-赋值”运算符来简化代码,如下:

flags &= MASK;

一种常见的C用法如下面语句所示:

ch &= 0xff;    /*或ch &= 0377*/

值0xff的二进制形式为11111111,八进制形式为0377。该掩码留下ch的最后8位,将其余位设为0。无论最初的ch是8、16或是更多位,都将最终的值修整到一个字节中。在这个例子中,掩码的长度是8位。

15.3.3  用法:打开位

有时,您可能需要打开一个值中的特定的位,同时保持其他值不变。例如,一台IBM PC通过将值发送到端口来控制硬件。比如打开扬声器,可能需要打开1位,同时保持其他位不变。您可以使用“位或”运算符来实现。

例如,考虑MASK,其位1设为1。下面的语句将flags中的位1设为1,其他位不变:

flags=flags | MASK;

这是因为任何位使用|运算符与0相组合结果为该位本身,任何位使用|运算与1组合结果为1。

15.3.4  用法:关闭位

不影响其他位,同时能够将特定的位关闭与能够将特定的位打开一样是有用的。假设您想关闭变量flags中的位1。MASK仍然只有位1是打开的。您可以做如下操作:

flags=flags & ~MASK;

因为MASK除了位1其他位都为0,所以~MASK除了位1其他位都为1。任何位使用&与1组合结果都为该位本身,因此该语句除位1以外保留其他所有位不变。任何位使用&与0相组合结果为0,因此无论位1的初始值为何,都将其设为0。

15.3.5  用法:转置位

转置(toggling)一个位表示如果该位打开,则关闭该位;如果该位关闭,则打开该位。

您可以使用“位异或”来转置一个位。其思想是如果b是一个位(1或0),那么如果b为1则1^b为0,如果b为0则1^b为1。而且,无论b的值是0还是1,0^b为b。因此,如果使用^将一个值与掩码组合,那么该值中对应掩码为1的位被转置,对应掩码位为0的位不改变。要转置flags中的位1,您可以使用以下任意一个语句:

flags=flags^MASK;

flags^=MASK;

15.3.6  用法:查看一个位的值

假设需要查看一位的值。例如,flags的位1是否为1?您不应该简单地比较flags与MASK:

if(flags==MASK)

puts("Wow");    //不能正常工作

即使flags中的位1被设为1,flags中的其他位也会使结果为非真。您必须屏蔽flags中的其他位,以便只把flags中的位1与MASK相比较:

if((flags&MASK)==MASK)

puts("Wow!");

位运算符的优先级低于==,因此需要使用()。

为了避免信息漏过边界,位掩码至少应该与其所屏蔽的值有相同的宽度。

15.3.7  移位运算符

移位运算符将位向左或向右移。

一、左移:<<

左移运算符<<将其左侧数的值的每位向左移动,移动的位数由其右侧的操作数决定。空出的位用0填充,并且丢弃移出左侧操作数左端的位。在以下的例子中,每位向左移动两个位置。

(10001010)<<2  //表达式

(00101000)//结果

该操作产生一个新位值,但是不改变其操作数。例如,假设stonk为1,则stonk<<2为4,但是stonk仍为1。您可以使用左移-赋值运算符来实际改变一个变量的值。该运算符将变量中的位向左移动右侧值大小的位置。如下例:

int stonk=1;

int onkoo;

onkoo=stonk<<2;  /*将4赋值给onkoo*/

stonk<<=2;           /*将stonk变为4*/

二、右移:>>

右移运算符将其左侧的操作数的值的每位向右移动,移动的位数由其右侧的操作数决定。丢弃移出左侧操作数右端的位。对于unsigned类型,使用0填充左端空出的位。对于有符号类型,结果依赖于机器。空出的位可能用0填充,或者使用符号(最左端的)位的副本填充:

(10001010)>>2    //表达式,有符号值

(00100010)          //在某些系统上的结果值

(10001010)>>2   //表达式,有符号值 

(11100010)          //在另一些系统上的结果值 

对于无符号值 ,有以下结果:

(10001010)>>2        //表达式,无符号值

(00100010)               //所有系统上的结果值

每位向右移动两个位置,空出的位用0填充。

右移-赋值运算符(>>=)将左侧变量的位向右移动指定数量的位置,如下所示:

int sweet=16;

int ooosw;

ooosw=sweet>>3;    /*ooosw=2,sweet仍然为16*/

sweet>>=3;              /*sweet变以2*/

三、用法:移位运算符

移位运算符能够提供快捷、高效的(依赖于硬件)对2的幂的乘法和除法。

number<<nnumber乘以2的n次幂
number>>n

如果number非负,则用number除以2的n次幂

这些移位运算符类似于在十进制中移动小数点来乘以或除以10。

移位运算符也用于从较大的单位中提取多组比特位。例如,假设您使用一个unsigned long值代表颜色值,其中低位字节存放红色亮度,下一个字节存放绿色亮度,第三个字节存放蓝色亮度。假设随后您希望将每种颜色的亮度存放在各自的unsigned char变量中。那么您可以使用下列语句:

#define BYTE_MASK oxff 

unsigned long color = 0x002a162f;

unsigned char blue,green,red;

red=color & BYTE_MASK;

green=(color>>8) & BYTE_MASK;

blue=(color>>16) & BYTE_MASK;

这段代码使用右移运算符将8位颜色值移动到低位字节,然后使用掩码技术将低位字节赋给所需的变量。

15.3.8  编程实例

使用移位运算符将数字转化为它的二进制表示形式。

程序清单15.1中的程序从键盘读取一个整数,将该整数和一个字符串地址传给一个名为itobs()的函数。然后该函数使用移位运算符计算出正确的1和0的组合,并存放到字符串中。

程序清单15.1  binbit.c程序 

/*使用位运算显示二进制数*/

#include <stdio.h>
char * itobs(int ,char *);
void show_bstr(const char *);

int main(void)
{
    char bin_str[8*sizeof(int)+1];
    int number;

    puts("Enter integers and see them in binary.");
    puts("Non-number input terminates program.");

    while(scanf("%d",&number)==1)
    {
        itobs(number,bin_str);
        printf("%d is ",number);
        show_bstr(bin_str);
        putchar('\n');
    }
    puts("Bye!");

    return 0;
}

char * itobs(int n,char *ps)
{
    int i;
    static int size = 8*sizeof(int);

    for(i=size-1;i>=0;i--,n>>=1)
    {
        ps[i]=(01&n)+'0';
    }
    ps[size]='\0';

    return ps;
}

void show_bstr(const char * str)
{
    int i=0;
    while(str[i])
    {
        putchar(str[i]);
        if(++i%4==0 && str[i])
            putchar(' ');
    }
}

程序清单15.1假设系统使用8位表示一个字节。因此,表达式8*sizeof(int)是一个int位数。考虑到结尾的空字符,bin_str数组的元素个数为这个表达式的值再加1.

因为itobs()函数返回的地址与传送给该函数的地址相同,所以您可以将该函数作为printf()的参数使用。首次执行for循环时,该函数求01&n的值。01是一个掩码的八进制表示形式,该掩码除位0之外的其他位都是0.因此,01&n就是n的最后一位的值。该值 为0或1,但是字符数组需要的是字符‘0’或字符'1'。对该值加上'0'的ASCII编码就可以完成该转换。结果放置在数组的倒数第2个元素中(保留最后的元素存放空字符)。

顺便提一下,您也可以使用1&n代替01&n。使用八进制的1而不是十进制的1看起来会更接近计算机一些。

然后,该循环执行语句i--和n>>=1。第一个语句移动到数组的前一个元素,第二个语句将n中的位向右移动一个位置。下次执行循环时,代码得到新的最右端的位的值。然后,将相应的数字字符放置在最后数字前面的元素中。使用这种方式,该函数从右向左填充数组。

您也可以使用printf()或puts()函数来显示结果字符串,而程序清单15.1定义了show_bstr()函数,它把每4位分成一组,以便于读出字符串。

15.3.9  另一个实例

这次的目的是编写一个函数,该函数反转一个值中的最后的n位,参数为n和要反转的值。

~运算符可以反转位,但是该运算符反转一个字节中的所有的位,而不是选定的少数位。^运算符(异或)可以用于转置单个位。假设您创建一个掩码,该掩码的最后n位设为1,其余位设为0.然后,对该掩码和一个值使用^运算就可以转置(即反转)这个值的最后n位,同时保留该值的其他位不变。这就是下面所使用的方法:

int invert_end(int num,int bits)
{
    int mask = 0;
    int bitval = 1;

    while(bits-- > 0)
    {
        mask |= bitval;
        bitval<<=1;
    }
    return num^mask;
}

while 循环创建该掩码。最初,mask所有位都被设为0.第一次执行循环将位0设为1,然后将bitval增加到2;也就是将bitval的位0设为0,位1设为1。下次执行循环时,将mask的位1设为1,依此类推。最后,num^mask运算产生所需的结果。

要测试该函数,你可以将其插入前面的程序,如程序清单15.2所示:

#include <stdio.h>
char * itobs(int n,char * ps);
void show_bstr(const char *);
int invert_end(int num,int bits);

int main(void)
{
    char bit_str[8*sizeof(int)+1];
    int number;

    puts("Enter integers and see them in binary.");
    puts("Non-numeric input terminates program.");
    while(scanf("%d",&number)==1)
    {
        itobs(number,bit_str);
        printf("%d is \n",number);
        show_bstr(bit_str);
        putchar('\n');

        number=invert_end(number,4);
        printf("Inverting the last 4 bits gives\n");
        show_bstr(itobs(number,bit_str));
        putchar('\n');
    }
    puts("Bye");

    return 0;
}

char * itobs(int n,char * ps)
{
    int i;
    static int size=8*sizeof(int);

    for(i=size-1;i>=0;i--,n>>=1)
      ps[i]=(01&n)+'0';
    ps[size]='\0';

    return ps;
}

void show_bstr(const char * str)
{
    int i=0;

    while(str[i])
    {
        putchar(str[i]);
        if(++i%4==0 && str[i])
            putchar(' ');
    }
}

int invert_end(int num,int bits)
{
    int mask=0;
    int bitval=1;

    while(bits-- >0)
    {
        mask|=bitval;
        bitval<<=1;
    }
    return num^mask;
}

 

转载于:https://my.oschina.net/idreamo/blog/948290

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值