4.8 位运算符(2)
定义quiz1的类型是unsigned long,这样,quiz1在任何机器上都将至少拥有32位;给quiz1赋一个明确的初始值,使得它的每一位在开始时都有统一且固定的值。
教师必须有权设置并检查每一个二进制位。例如,我们需要对序号为27的学生对应的位进行设置,以表示他通过了测验。为了达到这一目的,首先创建一个值,该值只有第27位是1其他位都是0,然后将这个值与quiz1进行位或运算,这样就能强行将quiz1的第27位设置为1,其他位都保持不变。
为了实现本例的目的,我们将quiz1的低阶位赋值为0、下一位赋值为1,以此类推,最后统计quiz1各个位的情况。
使用左移运算符和一个unsigned long类型的整数字面值1(参见2.1.3节,第38页)就能得到一个表示学生27通过了测验的数值:
- 1UL << 27 // 生成一个值,该值只有第27位为1
1UL的低阶位上有一个1,除此之外(至少)还有31个值为0的位。之所以使用unsigned long类型,是因为int类型只能确保占用16位,而我们至少需要27位。上面这条表达式通过在值为1的那个二进制位后面添加0,使得它向左移动了27位。
接下来将所得的值与quiz1进行位或运算。为了同时更新quiz1的值,使用一条复合赋值语句(参见4.4节,第147页):
- quiz1 |= 1UL << 27; // 表示学生27通过了测验
- | =运算符的工作原理和+=非常相似,它等价于
- quiz1quiz1 = quiz1 | 1UL << 27; // 等价于quiz1 |= 1UL << 27;
假定教师在重新核对测验结果时发现学生27实际上并没有通过测验,他必须要把第27位的值置为0。此时我们需要使用一个特殊的整数,它的第27位是0、其他所有位都是1。将这个值与quiz1进行位与运算就能实现目的了:
- quiz1 &= ~(1UL << 27); // 学生27没有通过测验
通过将之前的值按位求反得到一个新值,除了第27位外都是1,只有第27位的值是0。随后将该值与quiz1进行位与运算,所得结果除了第27位外都保持不变。
最后,我们试图检查学生27测验的情况到底怎么样:
- bool status = quiz1 & (1UL << 27); // 学生27是否通过了测验?
我们将quiz1和一个只有第27位是1的值按位求与,如果quiz1的第27位是1,计算的结果就是非0(真);否则结果是0。
移位运算符(又叫IO运算符)满足左结合律
尽管很多程序员从未直接用过位运算符,但是几乎所有人都用过它们的重载版本来进行IO操作。重载运算符的优先级和结合律都与它的内置版本一样,因此即使程序员用不到移位运算符的内置含义,也仍然有必要理解其优先级和结合律。
因为移位运算符满足左结合律,所以表达式
- cout << "hi" << " there" << endl;
的执行过程实际上等同于
- ( (cout << "hi") << " there" ) << endl;
在这条语句中,运算对象"hi"和第一个<<组合在一起,它的结果和第二个<<组合在一起,接下来的结果再和第三个<<组合在一起。
移位运算符的优先级不高不低,介于中间:比算术运算符的优先级低,但比关系运算符、赋值运算符和条件运算符的优先级高。因此在一次使用多个运算符时,有必要在适当的地方加上括号使其满足我们的要求。
- cout << 42 + 10; // 正确:+的优先级更高,因此输出求和结果
- cout << (10 < 42); // 正确:括号使运算对象按照我们的期望组合在一起,输出1
- cout << 10 < 42; // 错误:试图比较cout和42!
最后一个cout的含义其实是
- (cout << 10) < 42;
也就是"把数字10写到cout,然后将结果(即cout)与42进行比较"。
4.8节练习
练习4.25:如果一台机器上int占32位、char占8位,用的是Latin-1字符集,其中字符'q'的二进制形式是01110001,那么表达式 'q'<<6的值是什么?
练习4.26:在本节关于测验成绩的例子中,如果使用unsigned int作为quiz1的类型会发生什么情况?
练习4.27:下列表达式的结果是什么?
unsigned long ul1 = 3, ul2 = 7;
(a) ul1 & ul2 (b) ul1 | ul2
(c) ul1 && ul2 (d) ul1 || ul2