C入门---位运算

程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算直接对整数在内存中的二进制位进行操作。由于位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快。

(1),与(&)运算

"&"运算通常用于二进制取位操作,例如一个数 & 1 的结果就是取二进制的最末位。这可以用来判断一个整数的奇偶,二进制的最末位为0表示该数为偶数,最末位为1表示该数为奇数。

(2),或(|)运算

”|“运算通常用于二进制特定位上的无条件赋值,例如一个数 | 1 的结果就是把二进制最末位强行变成1。如果需要把二进制最末位变成0,对这个数| 1之后再减1就可以了,其实际意义就是把这个数强行变成最接近的偶数。

(3),异或(^)运算

"^"运算通常用于对二进制的特定一位进行取反操作,因为异或可以这样定义: 0和1 异或0都不变,异或1则取反。

 "^"运算的逆运算是它本身,也就是说两次异或同一个数最后结果不变,即(a^b)^b = a。”^"运算可以用于简单的加密,比如你想对一个朋友说1314520,但怕别人知道,于是双方约定拿生日19800205作为密钥,1314520 ^ 19800205 = 20590165, 于是就把20590165告诉这个朋友。你的朋友再次计算20590165^19800205的值,得到1314520,于是它就明白了你的意图。

加法和减法互为逆运算,并且加法满足交换律。可以写出一个不需要临时变量的swap函数

void swap(int a, int b)

{

    a = a + b;

    b = a - b;

    a = a - b;

}

由于^的逆运算是它本身,于是就有了一个很特别的swap过程:

void swap(int a, int b)

{

    a = a ^ b;

    b = a ^ b;

    a = a ^ b;

}

(4),取反(~)运算

“~”运算的定义是把内存中的0和1全部取反。使用“~”运算时要格外小心,需要注意整数类型有没有符号。如果“~”的对象是无符号整数(不能表示负数),那么得到的值就是它与该类型上界的差。因为无符号类型的数是用00到$FFFF依次表示的。下面的程序返回65435。

#include <stdio.h>

int main()

{

    unsigned short a = 100;

    a = ~a;

    printf("%d\n",a);

    return 0;

}

如果“~”的对象是有符号的整数,情况就不一样了, 稍后会提到。

(5),左移(<<)运算

  a << b 表示把 a 转为二进制后左移 b 位(在后面添b个0)。如100的二进制为1100100,而110010000转成十进制是400.可以看出 a<< b的值实际上就是a 乘以 2的 b次方,因为在二进制数后面添加一个0 就相当于该数乘以2.

通常认为 a <<1 比 a * 2更快,因为前者是更底层的操作,因此程序中乘以2的操作请尽量用左移一位来代替。

定义一些常量可能会用到 <<运算。可以方便地用(1 << 16) -1来表示65535.很多算法和数据结构要求数据规模必须是2的幂,此时可以用<<来定义Max_N等常量。

(6),右移(>>)运算

    和<<相似,a >> b 表示二进制右移b位(去掉末b位),相当于a除以2的b次方(取整)。此外也经常用 >> 1来代替除以2,比如二分查找, 堆的插入操作等。想办法用>>代替除法运算,可以使程序效率大大提高。最大公约数的二进制算法用除以2操作来代替mod运算,效率可以提高60%。

常见的二进制位的变换操作

功能 示例 位运算
去掉最后一位 (101101--->10110)  x >> 1
在最后加一个0 (101101--->1011010)  x << 1
在最后加一个1 (101101--->1011011) (x << 1) + 1
把最后一位变成1 (101100--->101101) x | 1
把最后一位变成0 (101101--->101100) (x|1)-1
最后一位取反 (101101--->101100) x ^ 1
把右数第k位变成1 (101001--->101101, k = 3) x | (1 << (k -1))
把右数第k位变成0 (101101--->101001,k = 3) x & ~(1 <<(k -1))
右数第K位取反 (101001--->101101,K= 3)  x ^(1 << (k - 1))
取末位三位 (1101101--->101) x & 7 
取末K位 (1101101--->1101,k = 5) x & ((1 << k ) -1)
取右数第 k位 (1101101--->1, k = 4)  x >>(k -1) &1
把末K位变成1 (101001--->101111, k = 4)  x | ((1 << k ) -1)
末K位取反 (101001--->100110, k = 4) x ^ ((1<<k ) - 1)
去掉整数最右边的1 (100101111--->10010110) x & ( x - 1)
把右边连续的1变成0 (100101111--->100100000)  x & ( x + 1)
把右起第一个0 变成1 (100101111--->100111111)  x | (x + 1)
把右边连续的0变成1 (11011000--->11011111)  x | (x - 1)
去掉右起第一个1 的左边 (100101000--->1000)

x & (x ^(x -1))

取右边连续的1 (100101111--->1111)

(x ^(x+1)) >> 1

在实际的编程过程中,往往会用一个整数的不同位表示不同的数据信息。在访问该整数时,就需要通过位运算来获得或者改变整数的某几位数值。比如在windows中创建时使用的create数据结构:

struct{

    PIO_SECURITY_CONTEXT SecurityCOntext;

    ULONG Options;

    USHORT POINTER_ALIGNMENT FileAttributes;

    USHORT ShareAccess;

    ULONG POINTER_ALIGNMENT Ealength;

    PVOID EaBuffer;

    LARGE_INTEGER AllocationSize;

}Create;

通常会引用其中的Options 如下:

Data->Iopb->Parameters.Create.Options

ULONG Options 是一个Windows文件创建过程中的无符号长整数,指示在创建和打开文件时的不同选项。其中高8位指示了CreateDisposition参数(如FILE_OPEN, FILE_CREATE), 低24位指示了CreateOptions参数(如FILE_DELETE_ON_CLOSE)。

为了得到CreateDisposition的值,采取下面的位操作:

(Data->Iopb->Parameters.Create.Options >> 24) & 0x000000ff;

将该整数右移24位,再与0xff进行与操作, 即可获得CreateDisposition的值。

(2)将第n 位置位或清零。

#define BITN (1 << n)

置位: a |= BITN;

清零: a &= ~BITN;

(3)清除整数a最右边的1

方法: a & (a -1)

问题: 如何判断整数x的二进制中含有多少个1?

int func(x)

{

    int countx = 0;

    while(x)

    {

        countx ++;

        x = x & (x -1);

    }

    return countx;

}

(4)将一个整数对齐到n:

    a = (a + n -1) & ~(n -1)

 

posted @ 2016-12-30 00:20  cindy_zl  阅读(268)  评论(0编辑  收藏  举报