• C/C++的位运算符操作


    转载转载自:http://blog.csdn.net/HainuCrazy/archive/2008/08/20/2802490.aspx
    C\C++支持比较低阶的位运算,在是众人皆知的了。每本C\C++的教科书都会说到这部分的内容,不过都很简略,我想会有很多人不知道位运算用在什么地方。这个帖子就简略说说位运算的用处,更进一步的用法要大家自己去体会。而主要说的是操作标志值方面。
       考虑一个事物、一个系统、或者一个程序可能会出现一种或者几种状态。为了在不同的状态下,作出不同的行为,你可以设立一些标志值,再根据标志值来做判断。比如C++的文件流,你就可以设定一些标志值,ios::app, ios::ate, ios::binary, ios::in, ios::out, ios::trunc,并且可以将它用|组合起来创建一个恰当的文件流。你可能会将这些标志值定义为bool类型,不过这样要是设置的标志值一多,就会很浪费空间。

    而假如定义一个整型数值,unsigned int flags; 在现在的系统,flags应该是32位, 用1,2,3....32将位进行编号,我们可以进行这样的判断, 当位1取1时,表示用读方式打开文件,当位2取1时,表示用写方式打开文件,当位3取1时,用二进制方式打开文件....因为flags有32位,就可以设置32个不同的状态值,也相当于32个bool类型。这样一方面省了空间, 另一方面也多了个好处,就是如前面所说的,可以将标志值组合起来。
    //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    好啦,上面有点不清不楚的。下面看看到底怎么操作这些标志值。
    设想C++的类ios这样定义, 其实没有这个类,只有ios_basic类,typedef basic_ios<char> ios;

    class ios
    {
    public:
        enum    app 0x0001, ate 0x0002, binary 0x0004,
            in 0x0008,  out 0x0010, trunc 0x0020 };
        ....
    private:
        unsigned int flags;
    };

    注意上面enum语句中,每一个数值只有1位是1,其余是0,这个很重要,你可以将它化成2进制看看。

    现在将flags相应的位设置为1, 可以这样做 flags |= app。这个等于flags flags app, 为什么呢? app只有1位是1,其余是0,因为0 0, 0, 这样0对应的位是不变的。而1 1, 1, 1对应的位不论原来是什么状态,都一定为1。如果想要将几个位都设置为1,可以这样做 flags |= (app ate binary)。因为每个enum常数各有一位为1, 与运算之后就有3位为1,就如上面的分析,就可以将那3位都设置为1, 其余位不变。这个就是标志可以组合起来用的原因。也可以用+组合起来,原因在于(下面的数字是2进制)0001 0010 0100 0111 跟与运算结果一样。不过不提倡用+, 考虑(app ate binary)要是我不小心写多了个标志值,(app ate ate binary)结果还是正确的,如果用+的话,就会产生进位,结果就会错误。通常我们不知道原先已经组合了多少个标志值了,用或运算会安全。

    现在将flags对应的位设置为0, 可以这样做 flags &= ~app。相当于 flags flags (~app). app取反之后,只有1位是0,其余是1,做与运算之后,1对应的位并不会改变,0对应的为不管原来是1是0,都肯定为0,这样就将对应的位设置了0。同样同时设置几个标志位可以这样做,flags &= ~(app ate binary)。

    现在将flags对应的位,如果是1就变成0,如果是0就变成1,可以这样做 flags ^= app。同时设置几个标志位可以写成 flags ^= (app ate binary)。不再做分析了,不然就太罗嗦了。不过也给大家一个例子,你查查Ascii表,会发现对应的大小写字母是相差倒数第6位,可以用这样的函数统一的将大写变成小写,小写变成大写。
    void xchgUppLow(string& letters)
    {
            const unsigned int mask (1<<5);

            for (size_t i=0; i<letters.length(); i++)
                    letters[i] ^= mask;
    }
    前提是输入的string一定要全是字母, 而要想是操作字母,可以在原来基础上加个判断。

         好啦,上面已经可以设置flags的对应位值了,要是判断呢?可以这样写 if (flags app) 这样可以判断对应的位值是否为1, 因为C\C++语言中非0就真。app只有一位是1,其余是0,如果, flags的对应位也是0,在与操作下就得到结果0,反之非0,这样就可以判断标志位了。

    //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    上面关于标志值的操作就介绍完毕。其实在C++中已经有了个bitset了,没有必要去自己进行低阶的位运算,上面的四个操作在bitset中分别叫做set, reset, flip, test。不过在C中,这样的代码还很常见, 反正知道多点也没有坏处。

    用 windows API 编程,你也经常会碰到这样的标志值,要互相组合,可以用|, 也可以用+(只是建议用|,理由上面说了). 它的标志值也是这样定义的,不过用#define
    #define WS_BORDER    0x0001
    #define WS_CAPTION    0x0002
    ......
    当初我就是想不明白为什么可以用|或者用+来组合,现在知道了。

    (注:上面出现的数字是我自己作的,到底实际怎么定义其实没有关系,只要保证只有一位是1,其余是0就可以的了. 因为编程的时候用的是常量值,没有人这样笨去直接用数值的)

    //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    其实,位运算还有很多用处。比如移位相当于乘除2的幂数(不过通常编译器也将乘除2的幂数优化成汇编的移位指令,所以没有必要不要这样卖弄了。汇编的移位指令有两组,分别针对有符号和无符号的, 我猜想在C\C++的同一移位运算针对有符号整数和无符号整数的不同,会根据情况编译成不同的汇编移位指令,不过没有去证实), 其实移位更用得多的地方是去构造一个掩码, 比如上面的mask (1<<5);

    还有&运算,有时候可以用来求余数。比如 value (1<<4 1) 这相当于将value的高位全变成0了,效果等于 value 8. 

    还有值得一提的是^运算,它有个很特殊的性质。比如 ^= B, 变成另一个数,跟着再执行A ^= B,又变回原来的数了,不信你可以列真值表或者化简逻辑式看看。就因为这个性质,^有很多用途。比如加密,你将原文看成A, 用同一个B异或一次,就相当于加密,跟着在用B异或一次,相当于解密。不过这样是很容易破解就是了。要是一个B不够,还可以加个C, 比如A ^= B, ^= C, ^= C, ^= B, 恢复原状。

    下面一个小程序,用异或交换两个数字。
    int 3;
    int 4;

    ^= y;
    ^= x;
    ^= y;

    其实和止交换数字,连交换对象也可以的
    template <typename T>
    void swap(T& obj1, T& obj2)
    {
            const int sizeOfObj sizeof(T);
            char* pt1 (char*)&obj1;
            char* pt2 (char*)&obj2;

            for (size_t i=0; i<sizeOfObj; i++)
            {
                    pt1[i] ^= pt2[i];
                    pt2[i] ^= pt1[i];
                    pt1[i] ^= pt2[i];
            }
    }

    还有异或操作还可以用在图象的光栅操作。我们知道,颜色也是用二进制来表示的,对颜色进行不同的位运算,就可以得到不同的光栅。因为异或的特殊性质,我们用异或操作的光栅画了副图,跟着再在原来的地方画一次,那副图就刷除了。这样可以用来显示动画而不用保存原来的画像信息。以前我写过个双人的贪食蛇,就用了异或光栅。因为背景色是白色的,也就是全1,作A A, 所以用画刷画一次是画了设定的颜色,再画一次就恢复。最有趣的是两蛇相交的时候,颜色也会作异或叠加,产生一种新的颜色了,离开的时候也会自动恢复。
    //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    好啦,够长了,就停止吧。在最后再给大家一段代码,是用来看看对象在内存中的位值的。可以看看。
    string bitsOfUChar(unsigned char c)
    {
            const int numOfBitsInUChar 8;
            unsigned int mask (1<<7);
            string result(8, '0');

            for (size_t i=0; i<numOfBitsInUChar; i++)
            {
                    if mask c)
                            result[i] '1';

                    mask >>= 1;
            }

            return result;
    }

    template <typename T>
    string bitsInMemory(const T& obj)
    {
            int sizeOfObj sizeof(obj);
            unsigned char* pt (unsigned char*)&obj;
            string result;

            for (size_t i=0; i<sizeOfObj; i++)
            {
                    result += bitsOfUChar(pt[i]);
                    result += ';
            }

            return result;
    }

    比如bitsInMemory(12),会输出00001100 00000000 00000000 00000000, 我就知道我自己的机器是小尾顺序的了。

    ******************************************

    简单示例如:

    代码
    #include <windows.h>

    #define ParamA 0x0001
    #define ParamB 0x0002
    #define ParamC 0x0004
    #define ParamD 0x0008
    #define ParamE 0x0010
    #define ParamF 0x0020


    enum ENUM_TYPE
    {
        A
    =0x01,
        B
    =0x02,
        C
    =0x04,
        D
    =0x08,
    };
    void FunDefine(UINT param)
    {
        printf(
    "-----param-----%d\n",param);
        
    if(ParamA&param)
            printf(
    "执行ParamA操作\n");
        
    if(ParamB&param)
            printf(
    "执行ParamB操作\n");
        
    if(ParamC&param)
            printf(
    "执行ParamC操作\n");
        
    if(ParamD&param)
            printf(
    "执行ParamD操作\n");
        
    if(ParamE&param)
            printf(
    "执行ParamE操作\n");
        
    if(ParamF&param)
            printf(
    "执行ParamF操作\n");
    }
    void FunEnum(UINT param)
    {
        
    if(param&ENUM_TYPE::A)
            printf(
    "执行ENUM_TYPE::A操作\n");
        
    if(param&ENUM_TYPE::B)
            printf(
    "执行ENUM_TYPE::B操作\n");
        
    if(param&ENUM_TYPE::C)
            printf(
    "执行ENUM_TYPE::C操作\n");
        
    if(param&ENUM_TYPE::D)
            printf(
    "执行ENUM_TYPE::D操作\n");
    }
    int main()
    {
        FunDefine(ParamA
    |ParamC|ParamF);
        FunEnum(ENUM_TYPE::B
    +ENUM_TYPE::D);
        getchar();
        
    return 0;
    }
     

  • 相关阅读:
    54:代码审计-TP5框架审计写法分析及代码追踪
    53:代码审计-TP5框架及无框架变量覆盖反序列化
    52:代码审计-PHP项目类RCE及文件包含下载删除
    51:代码审计-PHP框架MVC类上传断点调试挖掘
    支配树学习笔记
    模拟费用流学习笔记
    python之元类、双下方法( 双下方法也叫魔术方法、 内置方法)
    java 注解
    java 反射
    java synchronized
  • 原文地址:https://www.cnblogs.com/cxwx/p/1801289.html
Copyright © 2020-2023  润新知