【C++基础】第二十六课:位运算符

位运算符,移位运算符,位求反运算符,位与、位或、位异或运算符

Posted by x-jeff on July 3, 2021

【C++基础】系列博客为参考《C++ Primer中文版(第5版)》C++11标准)一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.前言

位运算符作用于整数类型的运算对象,并把运算对象看成是二进制位的集合。

一般来说,如果运算对象是“小整型”,则它的值会被自动提升成较大的整数类型。运算对象可以是带符号的,也可以是无符号的。如果运算对象是带符号的且它的值为负,那么位运算符如何处理运算对象的“符号位”依赖于机器。而且,此时的左移操作可能会改变符号位的值,因此是一种未定义的行为。

⚠️关于符号位如何处理没有明确的规定,所以强烈建议仅将位运算符用于处理无符号类型。

2.移位运算符

⚠️移位运算符右侧的运算对象一定不能为负(例如将下方程序改为auto a = bits << -1,编译就会报错),而且值必须严格小于结果的位数(例如将下方程序改为auto a = bits << 33,编译就会报错),否则就会产生未定义的行为。

1
2
3
4
5
6
7
8
int main() {
    unsigned char bits = 11;
    auto a = bits << 1;//a被自动提升为int
    auto b = bits >> 1;//b被自动提升为int
    cout << int(bits) << endl;//输出为:11
    cout << a << endl;//输出为:22
    cout << b << endl;//输出为:5
}

分析上方程序,数值11的二进制为0000 1011,左移一位为0001 0110,所以a为22;右移一位为0000 0101,所以b为5。

这里用的都是补码,正数的原码和补码是一样的。相关知识戳:【C++基础】第六课:类型转换

左移运算符(<<)在右侧插入值为0的二进制位。右移运算符(>>)的行为则依赖于其左侧运算对象的类型:如果该运算对象是无符号类型,在左侧插入值为0的二进制位;如果该运算对象是带符号类型,在左侧插入符号位的副本或值为0的二进制位,如何选择要视具体环境而定。

3.位求反运算符

位求反运算符(~)将运算对象逐位求反后生成一个新值,将1置为0、将0置为1:

char类型的运算对象首先提升成int类型,提升时运算对象原来的位保持不变,往高位添加0即可。因此在本例中,首先将bits提升成int类型,增加24个高位0,随后将提升后的值逐位求反。

4.位与、位或、位异或运算符

与(&)、或(|)、异或(^)运算符在两个运算对象上逐位执行相应的逻辑操作:

⚠️有一种常见的错误是把位运算符和逻辑运算符搞混了,比如位与(&)和逻辑与(&&)、位或(|)和逻辑或(||)、位求反(~)和逻辑非(!)。

5.使用位运算符

假设班级中有30个学生,可以使用一个二进制位代表某个学生是否通过了测试(1为通过,0为不通过):

1
unsigned long quiz1=0;

unsigned long在任何机器上都将至少拥有32位。使用一个数表示学生27通过了测试:

1
1UL<<27;//生成一个值,该值只有第27位为1

ULunsigned long

1UL为1,二进制为0000 .... 00011UL<<1为2,二进制为0000 .... 00101UL<<2为4,二进制为0000 .... 0100,剩余以此类推。

更新quiz1的值:

1
quiz1 |= 1UL << 27;//表示学生27通过了测试

|=运算符的工作原理和+=非常相似,它等价于:

1
quiz1 = quiz1 | 1UL << 27;

类似的还有&=^=

最后,再次核查学生27的测试结果:

1
bool status = quiz1 & (1UL << 27);//学生27是否通过了测试?

我们将quiz1和一个只有第27位是1的数值按位求与,如果quiz1的第27位是1,计算的结果就是非0(真);否则结果是0。

6.移位运算符(又叫IO运算符)满足左结合律

因为移位运算符满足左结合律,所以表达式:

1
cout << "hi" << " there" << endl;

的执行过程实际上等同于:

1
((cout << "hi") << " there") << endl;

移位运算符的优先级不高不低,介于中间:比算数运算符的优先级低,但比关系运算符、赋值运算符和条件运算符的优先级高。因此在一次使用多个运算符时,有必要在适当的地方加上括号使其满足我们的要求:

1
2
3
cout << 42+10;//正确:+的优先级更高,因此输出求和结果
cout << (10 < 42);//正确:括号使运算对象按照我们的期望组合在一起,输出1
cout << 10 < 42;//错误:试图比较cout和42!

最后一个cout的含义其实是:

1
( cout << 10) < 42;

也就是“把数字10写到cout,然后将结果(即cout与42进行比较)”。