位运算符作用于整数类型的运算对象,并把运算对象看成是二进制的集合。位运算符提供检查和设置二进制的功能。如以后将会介绍的bitset的标准库类型也可以表示任意二进制位集合,所以位运算符同样可以用于bitset类。
常见的位运算符:
按位与(&) 用法:expr1&expr2
按位或(|) 用法:expr1|expr2
按位异或(^) 用法:expr1^expr2
取反运算符(~) 用法:~expr
左移运算符(<<) 用法:expr1<<expr2
右移运算符(>>)用法:expr1>>expr2
一般来说,如果对象是“小整型”,则它的值会自动提升成为较大的整数类型(比如bool一般会提升到int)。运算对象可以是带符号的,也可以是不带符号的。如果运算对象是带符号的且它的值为负,那么位运算符如何处理运算对象的“符号位”依赖于机器,而且此时左移操作可能会改变符号位的值,因此这是一种未定义的行为。
PS:所以综上,我们应该把位运算仅仅用于处理无符号类型。
移位运算符
之前在处理输入和输出操作的时候,我们已经使用了标准IO库定义的<<运算符和>>运算符的重载版本。这两种运算的内置含义是对其运算对象执行基于二进制的移动操作,首先令左侧运算对象的内容按照右侧运算的对象的要求移动指定位数,然后将经过移动的(可能还进行过提升的)左侧运算对象的拷贝作为求值结果。其中,右侧的运算对象一定不能是负号,且值一定要严格小于结果的位数,否则就会产生未定义的行为。二进制位或者向左移(<<)向右移(>>),移除边界外的位数就被舍弃掉了。
左移运算符(<<)在右侧插入为0的二进制位,右移运算符(>>)的行为则依赖于其左侧运算对象的类型:如果为无符号类型,则在左侧加上为零的二进制位。如果为带符号类型的,则插入符号位的副本或者0的二进制位。
位求反运算符
位求反运算符(~)则是把对象逐位求反后生成的一个新值,将1置为0,把0置为1:
这边以10010111二进制char为例子:
编译器会先把char提升到int再进行取反,即转换为:
11111111 11111111 11111111 01101000
原本的数提升后的值为:
00000000 00000000 00000000 10010111
位与、位或、位异或 运算符
这里给两个数据
-
01100101
-
10101111
&运算 (24个0) 00100101
|运算 (24个0) 11101111
^运算 (24个0)11001010
- 对于&运算符,仅有两个对应的位置都是1,这个位置才是1,否则为0。
- 对于|运算符,如果两个运算位置只要有一个是1,就是1,否则就是0.
- 对于^运算符来说,这两个位置当且仅有一个是1,他才是1。
移位运算符(又称IO运算符)满足左结合律
尽管很多程序员从未直接使用过位运算符,但是几乎所有人都使用过它们的重载版本进行IO操作。重载运算符的优先级和结合律都和它的内置版本一样,因此即使程序员用不到移位运算符的内置含义。也仍然有必要理解其优先级和结合律:
因为移位运算符满足左结合律。所以表达式:
cout<<“hi”<<“there”<<endl;
的执行过程实际上等同于:
((cout<<“hi”)<<“there”)<<endl;
在这条语句中,运算对象“hi”和第一个<<结合在一起,它的结果和第二个<<结合在一起,接下来的结果和第三个<<结合在一起。
移位运算符的优先级不高不低,介于中间,比算术运算符优先级低,但比关系运算符、赋值运算符优先级高。因此在一次性使用多个运算符的时候,有必要在适当的地方加上括号使其满足我们的要求。
最后给一段代码,仅供大家参考一下:
#include<iostream>
using namespace std;
int main()
{
unsigned long u1 = 3, u2 = 7;
//u1:0011 u2: 0111
cout << (u1 & u2) << endl;
//0011 3
cout << (u1 | u2) << endl;
//0111 7
cout << (u1 && u2) << endl;
//1
cout << (u1 || u2) << endl;
//1
return 0;
}