必备技能7.7:位运算符
C++语言的设计考虑到了要支持对计算机硬件的访问,其中很重要的一点就是直接操作字节或者字中的比特位。C++中的位运算符就能实现这一点。位运算指的是对C++字符类型和整型类型对应的字节或者字中的位进行检测,设置或者移位的操作。位运算符不能使用于bool,float,double,long double,void等其它复杂的数据类型。在很多系统级别的编程中,位运算是很重要的,特别是在需要查询或者设置设备状态的时候。表格7-1列出了位运算符,其中对每一个为运算符都进行解释.
位运算符 | 含义 |
& | 位与 |
| | 位或 |
^ | 位异或 |
~ | 非 |
>> | 右移位 |
<< | 左移位 |
表格7-1 位运算符
位与,位或,位异或和非
位与,位或和非运算与其对应的逻辑运算的真值表是相同的,不同之处在于它们是逐位进行运算的。位异或运算符的真值表如下:
p | q | p^q |
0 | 0 | 0 |
1 | 0 | 1 |
1 | 1 | 0 |
0 | 1 | 1 |
位与最常用的就是用来算来把一个数中的某些位置0。这是由于只要两个运算数中对应比特位中只要有一个数为0,其运算结果就是0。如下:
1 1 0 1 0 0 1 1
& 1 0 1 0 1 0 1 0
----------------------
1 0 0 0 0 0 1 0
下面的程序通过把第六比特位的值设置为0来实现从小写字母到大写字母的转换。正如在ASCII字符集中定义的那样,小写字母的值比大写字母的值大32。因此,把小写字母转换为大写字母的时候只需要关闭第六个比特位即可,正如程序演示的那样:
//使用位与运算来实现小写字母到大写字母的转换
#include <iostream>
using namespace std;
int main()
{
char ch;
for ( int i = 0; i < 10; i++ )
{
ch = 'a' + i;
cout << ch;
//下面的这条语句就是把第六比特位关闭,也就是设置为.
ch = ch & 223; // ch 就变成大写的字母了
cout << ch << " ";
}
cout << "\n";
return 0;
}
程序的输出如下:
aA bB cC dD eE fF gG hH iI jJ
其中在使用位与运算符的语句中用到了值223,其二进制的形式为 1101 1111。这样一来,位与运算的结果就会使得第六比特位的值变为0,而其它比特位的值都保持不变。
当我们需要判断某个比特位是打开还是关闭的时候,使用位与运算符此时也是非常有效地。例如,下面的语句就可以检查第四个比特位是否被打开:
if (status & 8 ) cout << " bit 4 is on";
这里使用到了数字8,这是因为它的二进制形式为0000 1000。也就是说8的二进制中只有第四位的值是1,其它都是0。因此,if语句中的表达式只有在status的第四比特位也是1的时候才为真。这个特性的一个有趣的应用就是下面的show_binary()函数。它以二进制的形式输出参数的值。本章节的最后,我们会用到这个函数的输出来检查其它位运算符的结果。
//显示一个字节中的比特位的值
void show_binary(unsigned int u )
{
int t;
for ( t = 128; t > 0; t = t/2 )
{
if ( u & t ) cout << "1 ";
else cout << "0 ";
}
cout << "\n";
}
函数show_binary()通过使用位与运算符连续地从高位到低位逐个检测每个比特位的值。如果该位的值是打开的,则输出1,否则输出0。
位或运算和位与恰好相反,它可以用来打开比特位。任意一个运算数中的1值都可以使得运算结果中对应的比特位的值为1。例如,
1 1 0 1 0 0 1 1
| 1 0 1 0 1 0 1 0
-----------------------
1 1 1 1 1 0 1 1
我们可以使用位或运算来把大写字母转换为小写字母,如下:
//使用位或运算来实现大写字母到小写字母的转换
#include <iostream>
using namespace std;
int main()
{
char ch;
for ( int i = 0; i < 10; i++ )
{
ch = 'A' + i;
cout << ch;
//下面的这条语句将第六比特位打开,也就是设置为
ch = ch | 32; // ch 就变成小写的字母了
cout << ch << " ";
}
cout << "\n";
return 0;
}
程序的输出如下:
Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj
当第六比特位被设置为1的时候,每一个大写字母就被转换成了对应的小写字母了。
位运算中的异或,也常被缩写成XOR。异或只有在两个运算数对应比特位上的值不相同的时候,其结果对应位才是1,其它情况,结果对应比特位都是0。如下:
0 1 1 1 1 1 1 1
^ 1 0 1 1 1 0 0 1
------------------
1 1 0 0 0 1 1 0
XOR有一个很有意思的特性:当一个值X和另外一个值Y进行XOR运算后,其结果再次和Y做XOR运算就可以得到X的值。我们可以利用这一点来对消息进行编码。也就是如果:
R1 = X ^ Y;
R2 = R1 ^ Y;
那么R2的值和X的值是一样的。也就是连续两次使用XOR与同一个数进行运算将得到最初的那个运算数。我们可以使用这个特性来创建一个简单的加密程序:其中使用一个整型数作为关键字来通过XOR运算对字符消息进行加密和解密。加密的时候第一次使用XOR运算来产密文;解密时再次使用XOR运算产生明文。下面的程序就使用上述方法来完成加密和解密的:
//使用XOR来对消息进行加密和解密
#include <iostream>
using namespace std;
int main()
{
char msg[] = "This is a test";
char key = 88;
cout << "Original message: " << msg << "\n";
for ( int i = 0; i < strlen(msg); i++)
{
msg [i] = msg[ i ] ^ key ; //生成密文
}
cout << "Encoded message: " << msg << "\n";
for ( int i = 0; i < strlen(msg); i++)
{
msg [i] = msg[ i ] ^ key ; //生成明文
}
cout << "Decoded message: " << msg << "\n";
return 0;
}
程序的输出如下:
Original message: This is a test
Encoded message: 01+x1+x9x,=+,
Decoded message: This is a test
正如上面的程序展示的那样,使用相同的key值进行两次XOR运算得到的就是解密后的消息。
单目运算符非则是对操作数中的所有比特位取反。例如,假设A的二进制位1001 0110, 那么~A的结果就是0110 1001。下面的程序通过显示一个数字及其它的逐位取反的结果来展示非运算,其中使用到了前面的show_binary()函数。
#include <iostream>
using namespace std;
void show_binary(unsigned int u);
int main()
{
unsigned u;
cout << "Enter a number between 0 and 255: ";
cin >> u;
cout << "Here's the number in binary: ";
show_binary(u);
cout << "Here is the complement of the number: ";
show_binary(~u);
return 0;
}
//显示一个字节中的比特位的值
void show_binary(unsigned int u )
{
int t;
for ( t = 128; t > 0; t = t/2 )
{
if ( u & t ) cout << "1 ";
else cout << "0 ";
}
cout << "\n";
}
程序输出结果如下:
Enter a number between 0 and 255 99
Here's the number in binary: 0 1 1 0 0 0 1 1
Here is the complement of the number: 1 0 0 1 1 1 0 0
由于&,|,^,~是直接作用于运算数的每一个比特位上的。因此,位运算符通常不像关系和逻辑运算符那样应用于条件表达式中。例如,如果x等于7,那么x&&8的结果是true,然而x&8的结果则是false。