zoukankan      html  css  js  c++  java
  • C语言位操作

    C语言位操作(转)

    在计算机程序中,数据的位是可以操作的最小数据单位,理论上可以用“位运算”来完成所有的运算和操作。一般的位操作是用来控制硬件的,或者做数据变换使用,但是,灵活的位操作可以有效地提高程序运行的效率。C语言提供了位运算的功能, 这使得C语言也能像汇编语言一样用来编写系统程序。

      位运算符C语言提供了六种位运算符:

      & 按位与

      | 按位或

      ^ 按位异或

      ~ 取反

      << 左移

      >> 右移

      1. 按位与运算 按位与运算符"&"是双目运算符。其功能是参与运算的两数各对应的二进位相与。只有对应的两个二进位均为1时,结果位才为1 ,否则为0。参与运算的数以补码方式出现。

      例如:9&5可写算式如下: 00001001 (9的二进制补码)&00000101 (5的二进制补码) 00000001 (1的二进制补码)可见9&5=1。

      按位与运算通常用来对某些位清0或保留某些位。例如把a 的高八位清 0 , 保留低八位, 可作 a&255 运算 ( 255 的二进制数为0000000011111111)。

    应用:

    a. 清零特定位 (mask中特定位置0,其它位为1,s=s&mask)

    b. 取某数中指定位 (mask中特定位置1,其它位为0,s=s&mask)

      2. 按位或运算 按位或运算符“|”是双目运算符。其功能是参与运算的两数各对应的二进位相或。只要对应的二个二进位有一个为1时,结果位就为1。参与运算的两个数均以补码出现。

       例如:9|5可写算式如下:

    00001001|00000101

    00001101 (十进制为13)可见9|5=13

    应用:

    常用来将源操作数某些位置1,其它位不变。 (mask中特定位置1,其它位为0 s=s|mask)

      3. 按位异或运算 按位异或运算符“^”是双目运算符。其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。参与运算数仍以补码出现,例如9^5可写成算式如下:

    00001001^00000101 00001100 (十进制为12)

    应用:

    a. 使特定位的值取反 (mask中特定位置1,其它位为0 s=s^mask)

    b. 不引入第三变量,交换两个变量的值 (设 a=a1,b=b1)

    目 标 操 作 操作后状态

    a=a1^b1 a=a^b a=a1^b1,b=b1

    b=a1^b1^b1 b=a^b a=a1^b1,b=a1

    a=b1^a1^a1 a=a^b a=b1,b=a1

      4. 求反运算 求反运算符~为单目运算符,具有右结合性。 其功能是对参与运算的数的各二进位按位求反。例如~9的运算为: ~(0000000000001001)结果为:1111111111110110

      5. 左移运算 左移运算符“<<”是双目运算符。其功能把“<< ”左边的运算数的各二进位全部左移若干位,由“<<”右边的数指定移动的位数, 高位丢弃,低位补0。 其值相当于乘2。例如: a<<4 指把a的各二进位向左移动4位。如a=00000011(十进制3),左移4位后为00110000(十进制48)。

    6. 右移运算 右移运算符“>>”是双目运算符。其功能是把“>> ”左边的运算数的各二进位全部右移若干位,“>>”右边的数指定移动的位数。其值相当于除2。

      例如:设 a=15,a>>2 表示把000001111右移为00000011(十进制3)。对于左边移出的空位,如果是正数则空位补0,若为负数,可能补0或补1,这取决于所用的计算机系统。移入0的叫逻辑右移,移入1的叫算术右移,Turbo C采用逻辑右移。

    main(){

     unsigned a,b;

     printf("input a number: ");

     scanf("%d",&a);

     b=a>>5;

     b=b&15;

     printf("a=%d b=%d ",a,b);

    }

      再看一例:

    main(){

     char a='a',b='b';

     int p,c,d;

     p=a;

     p=(p<<8)|b;

     d=p&0xff;

     c=(p&0xff00)>>8;

     printf("a=%d b=%d c=%d d=%d ",a,b,c,d);

    }

    浮点数的存储格式:

    浮点数的存储格式是符号+阶码(定点整数)+尾数(定点小数)

    SEEEEEEEEMMMMMMMMMMMMMMMMMMMMMMM

    即1位符号位(0为正,1为负),8位指数位,23位尾数位

    浮点数存储前先转化成2的k次方形式,即:

    f = A1*2^k + A2*2^(k-1) + ... + Ak +... +An*2^(-m) (Ai = {0, 1}, A1 = 1)

    如5.5=2^2 + 2^0 + 2^(-1)

    其中的k就是指数,加127后组成8位指数位

    5.5的指数位就是2+127 = 129 = 10000001

    A2A3.....An就是尾数位,不足23位后补0

    所以5.5 = 01000000101000000000000000000000 = 40A00000

    所以,对浮点数*2、/2只要对8位符号位+、- 即可,但不是左移、右移

    关于unsigned int 和 int 的在位运算上的不同,下面有个CU上的例子描述的很清楚:

    [问题]:这个函数有什么问题吗?

    /////////////////////////////////////////////////

    /**

    * 本函数将两个16比特位的值连结成为一个32比特位的值。

    * 参数:sHighBits 高16位

    * sLowBits 低16位

    * 返回:32位值

    **/

    long CatenateBits16(short sHighBits, short sLowBits)

    {

    long lResult = 0; /* 32位值的临时变量*/

    /* 将第一个16位值放入32位值的高16位 */

    lResult = sHighBits;

    lResult <<= 16;

    /* 清除32位值的低16位 */

    lResult &= 0xFFFF0000;

    /* 将第二个16位值放入32位值的低16位 */

    lResult |= (long)sLowBits;

    return lResult;

    }

    /////////////////////////////////////////////////

    [问题的发现]:

    我们先看如下测试代码:

    /////////////////////////////////////////////////

    int main()

    {

    short sHighBits1 = 0x7fff;

    short sHighBits2 = 0x8f12;

    unsigned short usHighBits3 = 0xff12;

    short sLowBits1 = 0x7bcd;

    long lResult = 0;

    printf("[sHighBits1 + sLowBits1] ";

    lResult = CatenateBits16(sHighBits1, sLowBits1);

    printf("lResult = %08x ", lResult, lResult);

    lResult = CatenateBits16(sHighBits2, sLowBits1);

    printf("lResult = %08x ", lResult, lResult);

    lResult = CatenateBits16(usHighBits3, sLowBits1);

    printf("lResult = %08x ", lResult, lResult);

    }

    /////////////////////////////////////////////////

    运行结果为:

    [sHighBits1 + sLowBits1]

    lResult = 7fff7bcd

    lResult = 8f127bcd

    lResult = ff127bcd

    嗯,运行很正确嘛……于是我们就放心的在自己的程序中使用起这个函数来了。

    可是忽然有一天,我们的一个程序无论如何结果都不对!经过n个小时的检查和调试,最后终于追踪到……CatenateBits16() !?它的返回值居然是错的!!

    “郁闷!”你说,“这个函数怎么会有问题呢!?”

    可是,更郁闷的还在后头呢,因为你把程序中的输入量作为参数,在一个简单的main()里面单步调试:

    /////////////////////////////////////////////////

    int main()

    {

    short sHighBits1 = 0x7FFF;

    short sHighBits2 = 0x8F12;

    unsigned short usHighBits3 = 0x8F12;

    short sLowBits1 = 0x7BCD; //你实际使用的参数

    short sLowBits2 = 0x8BCD; //你实际使用的参数

    long lResult = 0;

    printf("[sHighBits1 + sLowBits1] ";

    lResult = CatenateBits16(sHighBits1, sLowBits1);

    printf("lResult = %08x ", lResult, lResult);

    lResult = CatenateBits16(sHighBits2, sLowBits1);

    printf("lResult = %08x ", lResult, lResult);

    lResult = CatenateBits16(usHighBits3, sLowBits1);

    printf("lResult = %08x ", lResult, lResult);

    printf(" [sHighBits1 + sLowBits2] ";

    lResult = CatenateBits16(sHighBits1, sLowBits2);

    printf("lResult = %08x ", lResult, lResult);

    lResult = CatenateBits16(sHighBits2, sLowBits2);

    printf("lResult = %08x ", lResult, lResult);

    lResult = CatenateBits16(usHighBits3, sLowBits2);

    printf("lResult = %08x ", lResult, lResult);

    return 0;

    }

    /////////////////////////////////////////////////

    发现结果竟然是:

    [sHighBits1 + sLowBits1]

    lResult = 7fff7bcd

    lResult = 8f127bcd

    lResult = 8f127bcd

    [sHighBits1 + sLowBits2]

    lResult = ffff8bcd //oops!

    lResult = ffff8bcd //oops!

    lResult = ffff8bcd //oops!

    前一次还好好的,后一次就ffff了?X档案?

    [X档案的真相]:

    注意那两个我们用来当作低16位值的sLowBits1和sLowBits2。

    已知:

    使用 sLowBits1 = 0x7bcd 时,函数返回正确的值;

    使用 sLowBits2 = 0x8bcd 时,函数中发生X档案。

    那么,sLowBits1与sLowBits2有什么区别?

    注意了,sLowBits1和sLowBits2都是short型(而不是unsigned short),所以在这里,sLowBits1代表一个正数值,而sLowBits2却代表了一个负数值(因为8即是二进制1000,sLowBits2最高位是1)。

    再看CatenateBits16()函数:

    /////////////////////////////////////////////////

    long CatenateBits16(short sHighBits, short sLowBits)

    {

    long lResult = 0; /* 32位值的临时变量*/

    /* 将第一个16位值放入32位值的高16位 */

    lResult = sHighBits;

    lResult <<= 16;

    /* 清除32位值的低16位 */

    lResult &= 0xFFFF0000;

    /* 将第二个16位值放入32位值的低16位 */

    lResult |= (long)sLowBits; //注意这一句!!!!

    return lResult;

    }

    /////////////////////////////////////////////////

    如果我们在函数中用

    printf("sLowBits = %04x ", sLowBits);

    打印传入的sLowBits值,会发现

    sLowBits = 0x7bcd 时,打印结果为

    sLowBits = 7bcd

    而sLowBits = 0x8bcd时,打印结果为

    sLowBits = ffff8bcd

    是的,即使用%04x也打印出8位十六进制。

    因此,我们看出来了:

    当sLowBits = 0x8bcd时,函数中 "lResult |= (long)sLowBits;" 这一句执行,会先将sLowBits转换为

    0xffff8bcd

    再与lResult做或运算。由于现在lResult的值为 0xXXXX0000 (其中XXXX是任何值),所以显然,无论sHighBits是什么值,最后结果都会是

    0xffff8bcd

    而当sLowBits = 0x7bcd时,函数中 "lResult |= (long)sLowBits;" 这一句执行,会先将sLowBits转换为

    0x00007bcd

    再与lResult做或运算。这样做或运算出来的结果当然就是对的。

    也就是说,CatenateBits16()在sLowBits的最高位为0的时候表现正常,而在最高位为1的时候出现偏差。

    [教训:在某些情况下作位运算和位处理的时候,考虑使用无符号数值——因为这个时候往往不需要处理符号。即使你需要的有符号的数值,那么也应该考虑自行在调用CatenateBits16()前后做转换——毕竟在位处理中,有符号数值相当诡异!]

    下面这个CatenateBits16()版本应该会好一些:

    /////////////////////////////////////////////////

    unsigned long CatenateBits16(unsigned short sHighBits, unsigned short sLowBits)

    {

    long lResult = 0;

    /* 将第一个16位值放入32位值的高16位 */

    lResult = sHighBits;

    lResult <<= 16;

    /* 清除32位值的低16位 */

    lResult &= 0xFFFF0000;

    /* 将第二个16位值放入32位值的低16位 */

    lResult |= (long)sLowBits & 0x0000FFFF;

    return lResult;

    }

    /////////////////////////////////////////////////

    注意其中的 "lResult |= (long)sLowBits & 0x0000FFFF;"。事实上,现在即使我们把CatenateBits16()函数的参数(特别是sLowBits)声明为short,结果也会是对的。
     

    c语言位操作的一些注意事项

    1. 位操作尽量使用unsigned char,而不是char,否则会使你混乱

    如果你使用char,那么一个普通的字符,0xe3,因为首位是1,所以当他被转换为16位长时,成了0xffffffe3,而不是我们想要的0x000000e3,因为他是一个有符号的负数。
    举例如下:
    #include <stdlib.h>
    #include <stdio.h>

    int main()
    {
    //    char buf[10] = {0};
        unsigned char buf[10] = {0};
        char sbuf[10] = {0};
        buf[0] = 0xe3;
        buf[1] = 0xb4;
        sbuf[0] = 0xe3;
        sbuf[1] = 0xb4;
        unsigned short pid1, pid2, pid3;
       
    /*bit operations with unsigned chars*/
        printf("bit operations with unsigned chars:\n");
        pid1= (buf[0]&0x1f);
        pid2= ((buf[0]&0x1f)<<8);
        pid3= ((buf[0]&0x1f)<<8)|buf[1];
       

        printf( "pid1 = %x\n", pid1  );
        printf( "pid2 = %2x\n", pid2  );
        printf( "pid3 = %2x\n", pid3  );


    /*bit operations with signed chars*/
        printf("bit operations with signed chars:\n");
        pid1= (sbuf[0]&0x1f);
        pid2= ((sbuf[0]&0x1f)<<8);
        pid3= ((sbuf[0]&0x1f)<<8)|sbuf[1];
       

        printf( "pid1 = %x\n", pid1  );
        printf( "pid2 = %2x\n", pid2  );
        printf( "pid3 = %2x\n", pid3  );

    }

    结果如下:
    [shaoting@serverbj6:/user/shaoting/DVB-T]$ ./a.out
    bit operations with unsigned chars:
    pid1 = 3
    pid2 = 300
    pid3 = 3b4
    bit operations with signed chars:
    pid1 = 3
    pid2 = 300
    pid3 = ffb4

    可见,pid3的两次取值,因为一个是针对unsigned char的buffer,另一个是针对char的buffer而使结果不同。


    2. 每次操作最好用括号括起来,不要随意猜想其算术优先级
    位操作的优先级比算数运算优先级低,如果记不清楚,就将其括起来,不要想当然,例子:

    #include <stdlib.h>
    #include <stdio.h>

    int main()
    {
        unsigned char buf[10] = {0};
        buf[0] = 0xf0;
        buf[1] = 0x03;
        unsigned short pid3, pid4;
       
        pid3= 5+ ((buf[0]&0x0f)<<8)|buf[1];
        pid4 =5+ (((buf[0]&0x0f)<<8)|buf[1]);

       

        printf( "pid3 = %2x\n", pid3  );
        printf( "pid4 = %2x\n", pid4  );
       
    }

    结果:
    [shaoting@serverbj6:/user/shaoting/DVB-T]$ ./a.out
    pid3 =  7
    pid4 =  8

    可见,我们以为pid3和pid4结果应该是一样的,都是8,但我们错了,pid3的计算结果其实是等于
    (5+ ((buf[0]&0x0f)<<8))|buf[1],即先进行了加法计算,在进行了位与计算。

    在对单处机进行编程的过程中,对位的操作是经常遇到的。C51对位的操控能力是非常强大的。从这一点上,
    可以看出C不光具有高级语言的灵活性,又有低级语言贴近硬件的特点。这也是在各个领域中都可以看到C的重
    原因。在这一节中将详细讲解C51中的位操作及其应用。
    1、位运算符
    C51提供了几种位操作符,如下表所示:
    1)“按位与”运算符(&)
    参加运算的两个数据,按二进位进行“与”运算。原则是全1为1,有0为0,即:
    0&0=0; 0&1=0; 1&0=0; 1&1=1;
    如下例:
    a=5&3; //a=(0b 0101) & (0b 0011) =0b 0001 =1
    那么如果参加运算的两个数为负数,又该如何算呢?会以其补码形式表示的二进制数来
    与运算。
    a=-5&-3; //a=(0b 1011) & (0b1101) =0b 1001 =-7 
    在实际的应用中与操作经常被用于实现特定的功能:
    1.清零
    “按位与”通常被用来使变量中的某一位清零。如下例:
    a=0xfe; //a=0b 11111110
    a=a&0x55; 
    //使变量a的第1位、第3位、第5位、第7位清零 a= 0b 01010100
    2.检测位
    要知道一个变量中某一位是‘1’还是‘0’,可以使用与操作来实现。
    a=0xf5; //a=0b 11110101
    result=a&0x08; //检测a的第三位,result=0
    3.保留变量的某一位
    要屏蔽某一个变量的其它位,而保留某些位,也可以使用与操作来实现。
    a=0x55; //a=0b 01010101
    运算符  含义  运算符  含义
    &  按位与  ~  取反
    |  按位或  <<   左移
    ^  按位异或  >>   右移

    a=a&0x0f; //将高四位清零,而保留低四位 a=0x05 
      2)“按位或”运算符(|)                       
                参与或操作的两个位,只要有一个为‘1’,则结果为‘1’。即有‘1’为‘1’
    ‘0’为‘0’。
                               0|0=0; 0|1=1; 1|0=1; 1|1=1;
    例如:
                    a=0x30|0x0f; //a=(0b00110000)|(0b00001111)=(0b00111111)=0x3f
    “按位或”运算最普遍的应用就是对一个变量的某些位置‘1’。如下例:
    a=0x00; //a=0b 00000000
    a=a|0x7f; //将a的低7位置为1,a=0x7f
    3)“异或”运算符(^)
                异或运算符^又被称为XOR运算符。当参与运算的两个位相同(‘1’与‘1’或
    与‘0’)时结果为‘0’。不同时为‘1’。即相同为0,不同为1。
               0^0=0; 0^1=1; 1^0=1;1^1=0;
    例如:
                    a=0x55^0x3f; //a=(0b01010101)^(0b00111111)=(0b01101010)=0x6a
    异或运算主要有以下几种应用:
                1.翻转某一位
                      当一个位与‘1’作异或运算时结果就为此位翻转后的值。如下例:

                      当一个位与‘1’作异或运算时结果就为此位翻转后的值。如下例:
    a=0x35; //a=0b00110101
    a=a^0x0f; //a=0b00111010 a的低四位翻转
                      关于异或的这一作用,有一个典型的应用,即取浮点的相反数,具体的实现
    下:
    f=1.23; //f为浮点型变量  值为1.23
    f=f*-1; //f乘以-1,实现取其相反数,要进行一次乘运算
    f=1.23;
    ((unsigned char *)&f)[0]^=0x80; //将浮点数f的符号位进行翻转实现取相反数 
              
                2.保留原值
                  当一个位与‘0’作异或运算时,结果就为此位的值。如下例:
    a=0xff; //a=0b11111111
    a=a^0x0f; //a=0b11110000 与0x0f作异或,高四位不变,低四位翻转
                3.交换两个变量的值,而不用临时变量
                  要交换两个变量的值,传统的方法都需要一个临时变量。实现如下:
    void swap(unsigned char *pa,unsigned char *pb)
    {
    unsigned char temp=*pa;//定义临时变量,将pa指向的变量值赋给它
    *pa=*pb;
    *pb=temp;  //变量值对调
    }
    而使用异或的方法来实现,就可以不用临时变量,如下: 

    void swap_xor(unsigned char *pa,unsigned char *pb)

    {
    *pa=*pa^*pb;
    *pb=*pa^*pb;
    *pa=*pa^*pb; //采用异或实现变量对调
    }
    从上例中可以看到异或运算在开发中是非常实用和神奇的。
      4)“取反”运算符(~)
                      与其它运算符不同,“取反”运算符为单目运算符,即它的操作数只有一个
    它的功能就是对操作数按位取反。也就是是‘1’得‘0’,是‘0’得‘1’。
                             ~1=0; ~0=1;
    如下例:
    a=0xff; //a=0b11111111
    a=~a; //a=0b00000000
    1.对小于0的有符号整型变量取相反数
    d=-1; 
    //d为有符号整型变量,赋值为-1,内存表示为0b 11111111 11111111
    d=~d+1; //取d的相反数,d=1,内存表示0b 00000000 00000001              
        此例运用了负整型数在内存以补码方式来存储的这一原理来实现的。负数的补码方式是这样
    的:负数的绝对值的内存表示取反加1,就为此负数的内存表示。如-23如果为八位有
    符号整型数,则其绝对值23的内存表示为0b00010111,对其取反则为0b11101000
    再加1为0b11101001,即为0XE9,与Keil仿真结果是相吻合的:
          2.增强可移植性
                        关于“增强可移植性”用以下实例来讲解:
                        假如在一种单片机中unsigned char类型是八个位(1个字节),那么一个此
    型的变量a=0x67,对其最低位清零。则可以用以下方法:
    a=0x67; //a=0b 0110 0111
    a=a&0xfe; //a=0b 0110 0110
    上面的程序似乎没有什么问题,使用0xfe这一因子就可以实现一个unsigned char型的变量最低位清
    零。但如果在另一种单片机中的unsigned char类型被定义为16个位(两个字节),
    那么这种方法就会出错,如下:
    b=0x6767; //假设b为另一种单片机中的unsigned char 类型变量,值为0b 0110
    0111 0110 0111
    b=b&0xfe; //如果此时因子仍为0xfe的话,则结果就为0b 0000 0000 0110 0110
    0x0066,而与0x6766不相吻合 
    上例中的问题就是因为不同环境中的数据类型差异所造成的,即程序的可移植性不好。对于这种
    况可以采用如下方法来解决: 
    a=0x67; //a=0b 0110 0111
    a=a&~1; //在不同的环境中~1将自动匹配运算因子,实现最后一位清零  a=0x66
    其中~1为 0b 11111110
    b=0x6767; //a=0b 0110 0111 0110 0111
    b=a&~1; //~1=0b 1111 1111 1111 1110,b=0b 0110 0111 0110 0110 ,即0x6766
    5)左移运算符(<<)

        左移运算符用来将一个数的各位全部向左移若干位。如:
                       a=a<<2
    表示将a的各位左移2位,右边补0。如果a=34(0x22或0b00100010),左移2位得0b10001000,即十
    的136。高位在左移后溢出,不起作用。
                        从上例可以看到,a被左移2位后,由34变为了136,是原来的4倍。而如果左
    移1位,就为0b01000100,即十进制的68,是原来的2倍,很显然,左移N位,就等
    乘以了2N。但一结论只适用于左移时被溢出的高位中不包含‘1’的情况。比如
    a=64; //a=0b 0100 0000
    a=a<<2; //a=0b 0000 0000
    其实可以这样来想,a为unsigned char型变量,值为64,左移2位后等于乘以了4,即64X4=256,
    种类型的变量在表达256时,就成为了0x00,产生了一个进位,即溢出了一个
    ‘1’。
                        在作乘以2N这种操作时,如果使用左移,将比用乘法快得多。因此在程序中
    适应的使用左移,可以提高程序的运行效率。
    6)右移运算符
                     右移与左移相类似,只是位移的方向不同。如:
                                       a=a>>1
    表示将a的各位向右移动1位。与左移相对应的,左移一位就相当于除以2,右移N位,就相当于除以
    2N。
                       在右移的过程中,要注意的一个地方就是符号位问题。对于无符号数右移时
    边高位移和‘0’。对于有符号数来说,如果原来符号位为‘0’,则左边高位为
    入‘0’,而如果符号位为‘1’,则左边移入‘0’还是‘1’就要看实际的编译
    了,移入‘0’的称为“逻辑右移”,移入‘1’的称为“算术右移”。Keil中采用
    “算术右移”的方式来进行编译。如下:

    d=-32; //d为有符号整型变量,值为-32,内存表示为0b 11100000
    d=d>>1;//右移一位 d为 0b 11110000 即-16,Keil采用"算术逻辑"进行编译
    7)位运算赋值运算符
                        在对一个变量进行了位操作中,要将其结果再赋给该变量,就可以使用位
    算赋值运算符。位运算赋值运算符如下:
    &=, |=,^=,~=,<<=, >>=
    例如:a&=b相当于a=a&b,a>>=2相当于a>>=2。
        8)不同长度的数据进行位运算
                        如果参与运算的两个数据的长度不同时,如a为char型,b为int型,则编译
    将二者按右端补齐。如果a为正数,则会在左边补满‘0’。若a为负数,左边补满
    ‘1’。如果a为无符号整型,则左边会添满‘0’。
    a=0x00; //a=0b 00000000
    d=0xffff; //d=0b 11111111 11111111
    d&=a; //a为无符号型,左边添0,补齐为0b 00000000 00000000,d=0b 00000000
    00000000

    C位操作

    programmi...   2008-03-18 14:56   阅读210   评论0  

    字号: 大大  中中  小小

    一、位的概念
         我们知道,在计算机中,一字节占8位(现在的某些电脑也有占16位的),这样表示的数的范围为0-255,也即00000000-11111111。位就是里面的0和1。
             char c=100;
         实际上c应该是01100100,正好是64H。其中高位在前,低位在后。
                      |       |
                    第7位   第0位

    二、位逻辑运算符

             符号          描述
              &            位逻辑与
              |            位逻辑或
              ^            位逻辑异或
              ~            取补

         表中除去最后一个运算符是单目运算符,其他都是双目运算符。这些运算符只能用于整型表达式。位逻辑运算符通常用于对整型变量进行位的设置、清零、取反、以及对某些选定的位进行检测。在程序中一般被程序员用来作为开关标志。较低层次的硬件设备驱动程序,经常需要对输入输出设备进行位操作。

              & 运算的规则是当两个位都为1时,结果为1,否则为0;
              | 运算的规则是当两个位都为0时,结果为0,否则为1;
              ^ 运算的规则是当两个位相同时,结果为0,否则为1;
              ~ 运算的规则是当为1时结果为0,当为0时,结果为1。

         设置位:设置某位为1,而其他位保持不变,可以使用位逻辑或运算。
             char c;
             c=c|0x40;
         这样不论c原先是多少,和01000000或以后,总能使第6位为1,而其他位不变。

         清除位:设置某位为0,而其他位保持不变。可以使用位逻辑与运算。
             c=c&0xBF;
         这样c和10111111与以后,总能使第6位为0,其他位保持不变。
         那如果想让某位为1,其他位都为0怎么办呢?

    三、位移运算符
              符号              描述
               <<               左移
               >>               右移

         位移运算符作用于其左侧的变量,其右侧的表达式的值就是移动的位数,运算结果就是移动后的变量结果。
             b=a<<2;
         就是a的值左移两位并赋值为b。a本身的值并没有改变。
                   
         向左移位就是在低位沙锅补0,向右移位就是在高位上补0。右移时可以保持结果的符号位,也就是右移时,如果最高位为1,是符号位,则补1而不是补0。
         程序员常常对右移运算符来实现整数除法运算,对左移运算符来实现整数乘法运算。其中用来实现乘法和除法的因子必须是2的幂次。

         举例:输入一个整数,判断这个数中有几个二进制位1?例如输入67,输出结果应该为3。因为67的相应二进制数为00000000 01000011(0043H),有3个1出现。
         分析:要判断是不是1,只需要判断该位与1与以后是不是1就可以知道。一个整数,判断16次即可。

             main()
             {
                 int num,k;
                 int count=0;                /* 记录1的个数 */
                 scanf("%d",&num);
                 for(k=0;k<16;k++)
                 {
                     if(num&1==1) count++;     /* 判断最低位是不是1 */
                     num>>=1;                  /* num右移1位 */
                 }
                 printf("%d\n",count);
             }

         这样每次都判断最低位是不是1,判断完以后,让前面的右移一位即可。
         对位的操作,一般程序中用的不多,但是在对计算机硬件操作时,肯定会涉及到。例如,我们以后要讲到的对串口和声卡操作就要用到一些。


    本文来自CSDN博客:http://blog.csdn.net/exceptionist/archive/2010/02/10/5305312.aspx

  • 相关阅读:
    Attach Files to Objects 将文件附加到对象
    Provide Several View Variants for End-Users 为最终用户提供多个视图变体
    Audit Object Changes 审核对象更改
    Toggle the WinForms Ribbon Interface 切换 WinForms 功能区界面
    Change Style of Navigation Items 更改导航项的样式
    Apply Grouping to List View Data 将分组应用于列表视图数据
    Choose the WinForms UI Type 选择 WinForms UI 类型
    Filter List Views 筛选器列表视图
    Make a List View Editable 使列表视图可编辑
    Add a Preview to a List View将预览添加到列表视图
  • 原文地址:https://www.cnblogs.com/pbreak/p/1775724.html
Copyright © 2011-2022 走看看