### 位运算符

在计算机中，数据都是以二进制形式表示，以字节为最小单位进行存储。一个字节分为 8 位，每一位可以表示一个二进制数 0 或 1。为了能够对一个字节中的某一位或几位进行操作，C++提供了 6 种位运算符，如表 1 所示。

表 1 C++位运算符

位运算符 | 描述
& | 按位与运算
| | 按位或运算
^ | 按位异或
~ | 按位取反
<< | 左移运算
>> | 右移运算

q 按位与运算——&

按位与运算是指两个数据对应的二进位如果都为 1，则该位的结果为 1，否则该位的结果为 0。即 0&0 结果为 0，0&1 结果为 0，1&0 结果为 0，1&1 结果为 1。下面通过两个数字的与运算——“6&12”来分析与运算的过程。数字 6 对应的二进制表示为“00000110”，数字 12 对应的二进制数为“00001100”。它们的与运算过程如图 1 所示。

图 1 与运算示意图

从图 1 中可以发现，“00000110”与“00001100”的与运算结果为“00000100”，即数值 4。也就是说 6&12 的结果为 4。

在程序中使用与运算可以进行清零操作。例如，将某个字节数据清零。可以将该字节的数据与 0 进行与运算，其结果将为 0。因为任何数与 0 进行与运算，结果必然为 0。此外，与运算还可以将一个字节中的某些位保留下来。例如，对于二进制数 00001010，想要保留其 1、3、5、7 位，则可以将该二进制数与“01010101”二进制（该二进制数 1、3、5、7 位为 1，其他位为 0）进行与运算。

使用与运算还可以用于某个属性集合中的一个属性的测试。例如，一个对话框可以包含边框、标题栏、子窗口、水平滚动条等风格。为了检测当前的对话框是否包含一个具体的风格，例如检测对话框是否包含边框风格，可以使用与运算来实现。首先定义一组窗口风格。

#define WS_BORDER 1 //边框风格

#define WS_CAPTION 2 //标题栏风格

#define WS_CHILD 4 //子窗口风格

#define WS_HSCROLL 8 //水平滚动条风格

然后定义一个表示窗口风格的整型变量。

int nStyle = WS_BORDER|WS_CHILD; //定义一个整型变量，并初始化

最后使用与运算测试窗口的风格。

if (nStyle & WS_BORDER == true) //测试是否包含 WS_BORDER 风格

{

cout<< "窗口具有边框风格"

}

注意：在定义类似于窗口风格这样的标记时，由于这些标记可以组合在一起。为了区分一个集合中的每一个标记，在定义每一个标记时，其数值是有要求的。例如WS_BORDER的数值为1，二进制形式为00000001，WS_CAPTION的数值为2，二进制形式为00000010，WS_CHILD的数值为4，而进制形式为00000100，WS_HSCROLL的数值为8，二进制形式为00001000。这些标记的二进制位只有一个1，并且1出现在不同的二进制位上。如图2所示。

图 2 二进制标记

这样，如果窗口中包含了 WS_BORDER、WS_CAPTION 和 WS_HSCROLL3 个风格时，可以使用或运算组合（或运算稍后进行介绍）。例如：

int nStyle = WS_BORDER|WS_CAPTION|WS_HSCROLL;

那么 nStyle 的结果为 11，二进制形式为“00001011”。在测试 nStyle 是否包含 WS_BORDER 风格时，可以使用与运算——nStyle & WS_BORDER，运算过程如图 3 所示。

图 3 与运算过程

运算结果不为 0，表示 nStyle 中包含 WS_BORDER 风格，为 0，则 nStyle 中不包含 WS_BORDER 风格。

q 按位或运算——|

按位或运算是指两个相应的二进位只要有一个结果为 1，则结果为 1，否则结果为 0。即 0&0 结果为 0，0&1 结果为 1，1&0 结果为 1，1&1 结果为 1。例如，将数字 6 与 12 进行或运算——6 | 12。运算过程如图 4 所示。

图 4 或运算过程

从图 4 中可以发现，6 与 12 的或运算结果为 14。

q 异或运算——^

按位异或运算是指两个相应的二进位均相同，则结果为 0，否则结果为 1。即 0&0 结果为 0，0&1 结果为 1，1&0 结果为 1，1&1 结果为 0。例如，将 6 和 12 进行异或运算，即“6 ^ 12”。其运算过程如图 5 所示。

图 5 按位异或运算

从图 5 中可以发现，“6 ^ 12”的运算结果为 10。在实际应用中，通常利用异或运算来实现二进位的反转。例如，将二进制数“00001010”的后 4 位反转，可以将其与“00001111”二进制数（后 4 位均为 1）进行异或运算。此外，还可以利用异或运算实现两个变量值的互换（不使用中间变量）。首先分析一下异或运算的性质：

1.任何数据与0进行异或运算，结果仍为数据本身。

2.变量与自身进行异或运算，结果为 0。

3.异或运算具有交换性。即a^b^c等于a^c^b，还等于b^a^c等。

下面通过一个示例演示如何使用异或运算实现两个变量值的互换。

#include "stdafx.h"

#include "iostream.h"

void main()

{

int nLen = 50; //定义一个变量 nLen，初始值为 50

int nHeight = 40; //定义一个变量 nHeight，初始值为 40

cout << "转换前 nLen = " << nLen << endl; //输出 nLen

cout << "转换前 nHeight = " << nHeight << endl; //输出 nHeight

nLen = nLen ^ nHeight; //nLen 与 nHeight 进行异或运算，结果赋值给 nLen

nHeight = nHeight ^ nLen; //nHeight 与 nLen 进行异或运算，结果赋值给 nHeight

nLen = nLen ^ nHeight; //nLen 与 jvar 进行异或运算，结果赋值给 nLen

cout << "转换后 nLen = " << nLen <<endl; //输出 nLen

cout << "转换后 nHeight = " << nHeight << endl; //输出 nHeight

}

运行程序，效果如图 6 所示。

图 6 交换变量值

分析上述黑体部分代码，首先观察黑体部分第 2 行代码“nHeight = nHeight ^ nLen;”，根据上一行代码将“nLen”替换为“nLen ^ nHeight”，那么第 2 行代码“nHeight = nHeight ^ nLen;”转换为“nHeight = nHeight ^ nLen ^ nHeight;”由于异或运算具有交换性，因此，也可以写为“nHeight = nHeight ^ nHeight^ nLen;”。由于变量与自身异或运算，结果为 0，因此第 2 行代码又可以转换为“nHeight = 0^ nLen;”，根据任何数据与0进行异或运算，结果仍为数据本身，最终，第 2 行代码的结果为“nHeight = nLen”。接着分析黑体部分第 3 行代码，根据第 1 行代码和第 2 行代码，可以将第 3 行代码转换为“nLen = nLen ^ nHeight ^ nHeight^ nLen ^ nHeight;”，即“nLen = nLen ^ nLen ^ nHeight ^ nHeight ^ nHeight;”，等价与“i nLen =0^ nHeight”，最终 nLen = nHeight。

q 按位取反运算——~

取反运算符~用于对一个二进制数按位取反，即将 0 转换为 1，将 1 转换为 0。例如，对二进制数“00001010”取反，结果为“11110101”。

q 左移运算符——<<

左移运算符用于将一个数二进制位左移若干位，右边补 0。例如，将 10 左移两位，表示为：

int nRet = 10 << 2;

其结果 nRet 等于 40。因为 10 的二进制数为“00001010”，左移 2 位后，表示为“00101000”，即 40。实际上，左移一位，表示乘以 2，左移两位，表示乘以 22，即乘以 4，以此类推。

在使用左移运算符时，有时会出现移出界的情况。例如：

char szVar = 129 ; //定义一个字符变量

szVar = szVar << 2 ; //将 129 左移两位

上述代码执行后，szVar 对应的数值将为 4。因为，129 对应的二进制数为“01000001”，将其左移两位为“00000100”，7、8 位“01”溢出，被舍弃，因此 szVar 的值为 4。

q 右移运算符——>>

右移运算符与左移运算符相反，表述将一个数的二进制位右移若干位。例如，将 10 右移两位，表示为：

int nRet = 10 >> 2;

其结果 nRet 等于 2。因为 10 对应的二进制数为“00001010”，右移 2 位后为“00000010”，即十进制 2。

位运算符还可以与赋值运算符=组合，形成位运算赋值运算符。例如“&=”、“|=”、“^=”等。以&=为例，nLen &= nHeight，实际上等价宇 nLen = nLen & nHeight。具体运算过程前文已经介绍，这里不再叙述。