为了避免篇幅过长,有三组运算符没有在本书正文部分介绍。第一组是按位运算符,能够操纵值中的各个位;这些运算符是从C语言继承而来的;第二组运算符是两个成员解除引用运算符,它们是C++新增的;第三组是C++11新增的运算符:alignof和noexcept。本附录将简要地对这些运算符做一总结。
按位运算符对整数值的位进行操作。例如,左移运算符将位向左移,按位非运算符将所有的1变成0,所有的0变成1,C++共有6个这样的运算符:<<、>>、~、&、|和^。
左移运算符的语法如下:
其中,value是要被操作的整数值,shift是要移动的位数。例如,下面的代码将值13的所有位都向左移3位:
腾出的位置用0填充,超出边界的位被丢弃(参见图E.1)。
由于每个位都表示右边一位的2倍(参见附录A),所以左移一位相当于乘以2。同样,左移2位相当于乘以22,左移n位相当于乘以2n。因此,13<<3的值为13 × 23,即104。
图E.1 左移运算符
左移运算符提供了通常可以在汇编语言中找到的功能。不过,左移运算符在汇编语言中会直接修改寄存器的内容,而C++左移运算符生成一个新值,而不修改原来的值。例如,请看下面的代码:
上述代码不会修改x的值。表达式x<<3使用x的值来生成一个新值,就像x+3会生成一个新值,而不会修改x一样。
如果要用左移运算符来修改变量的值,则还必须使用赋值运算符。可以使用常规的赋值运算符或<<=运算符(该运算符将移动与赋值结合在一起):
正如所期望的,右移运算符(>>)将位向右移,其语法如下:
其中,value是要移动的整数值,shift是要移动的位数。例如,下面的代码将值17中所有的位向右移两位:
对于无符号整数,腾出的位置用0填充,超过边界的位被删除。对于有符号整数,腾出的位置可能用0填充,也可能用原来最左边的位填充,这取决于C++实现(图E.2是一个用0填充的例子)。
图E.2 右移运算符
向右移动一位相当于除以2。向右移动n位相当于除以2n。
C++还定义了一个“右移并赋值”运算符,如果要用移动后的值替换变量的值,可以这样做:
在有些系统上,使用左移运算符(右移运算符)实现将整数乘(除)以2的速度比使用乘(除)法运算符更快,但由于编译器在优化代码方面越来越好,因此这种差异正在减小。
逻辑按位运算符类似于常规的逻辑运算符,只是它们用于值的每一位,而不是整个值。例如,请看常规的非运算符(!)和位非(或求反)运算符(~)。!运算符将true(或非零值)转换为false,将false值转换为true。~运算符将每一位转换为它的反面(1转换为0,0转换为1)。例如,对于unsigned char值3:
表达式!x的值为0。要知道~x的值,先把它写成二进制形式:00000011。然后将每个0转换为1,将每个1转换为0。这样将得到值11111100,在十进制中,为252(图E.3是一个16位的例子)。新值是原值的补值。
按位运算符OR(|)对两个整数值进行操作,生成一个新的整数值。如果被操作的两个值的对应位至少有一个为1,则新值中相应位为1,否则为0(参见图E.4)。
图E.3 按位非运算符
图E.4 按位运算符OR
表E.1对∣运算符的操作方式进行了总结。
表E.1 b1|b2的值
位 值 |
b1 = 0 |
b1 = 1 |
---|---|---|
b2 = 0 |
0 |
1 |
b2 = 1 |
1 |
1 |
运算符| =组合了按位运算符OR与赋值运算符的功能:
按位运算符XOR(^)将两个整数值结合起来,生成一个新的整数值。如果原始值中对应的位有一个(而不是两个)为1,则新值中相应位为1;如果对应的位都为0或1,则新值中相应位为0(参见图E.5)。
图E.5 按位运算符XOR
表E.2总结了^运算符的结合方式。
表E.2 b1^b2的值
位 值 |
b1 = 0 |
b1 = 1 |
---|---|---|
b2 = 0 |
0 |
1 |
b2 = 1 |
1 |
0 |
^ =运算符结合了按位运算符XOR和赋值运算符的功能:
按位运算符AND(&)将两个整数结合起来,生成一个新的整数值。如果原始值中对应位都为1,则新值中相应位为1,否则为0(参见图E.6)。
图E.6 按位运算符AND
表E.3总结了&运算符是如何运算的。
表E.3 b1&b2的值
位 值 |
b1 = 0 |
b2 = 1 |
---|---|---|
b2 = 0 |
0 |
0 |
b2 = 1 |
0 |
1 |
& =运算符结合了按位运算符AND和赋值运算符的功能:
对于几种按位运算符,C++提供了替代表示,如表E.4所示。它们适用于字符集中不包含传统按位运算符的区域。
表E.4 按位运算符的替代表示
标 准 表 示 |
替 代 表 示 |
---|---|
& |
bitand |
& = |
and_eq |
| |
bitor |
|= |
or_eq |
~ |
compl |
^ |
xor |
^ = |
xor_eq |
这些替代表示让您能够编写下面这样的语句:
控制硬件时,常涉及打开/关闭特定的位或查看它们的状态。按位运算符提供了执行这种操作的途径。下面简要地介绍一下这些方法。
在下面的示例中,lottabits表示一个值,bit表示特定位的值。位从右到左进行编号,从0开始,因此,第n位的值为2n。例如,只有第3位为1的整数的值为23(8)。一般来说,正如附录A介绍的,各个位都对应于2的幂。因此我们使用术语位(bit)表示2的幂;这对应于特定位为1,其他所有位都为0的情况。
下面两项操作打开lottabits中对应于bit表示的位:
它们都将对应的位设置为1,而不管这一位以前的值是多少。这是因为对1和1或者0和1执行OR操作时,都将得到1。lottabits中其他所有位都保持不变,这是因为对0和0做OR操作将得到0,对1和0做OR操作将生成1。
下面两项操作切换lottabits中对应于bit表示的位。也就是说,如果位是关闭的,则将被打开;如果位是打开的,将被关闭:
对0和1执行XOR操作的结果为1,因此将关闭已打开的位;对1和1执行XOR操作的结果为0,因此将打开已关闭的位。lottabits中其他所有位都保持不变,这是因为对0和0执行XOR操作的结果为0,对1和0执行XOR操作的结果为1。
下面的操作将关闭lottabits中对应于bit表示的位:
该语句关闭相应的位,而不管它以前的状态如何。首先,运算符~bit将原来为1的位设置为0,原来为0的位设置为1。对0和任意值执行AND操作都将得到0,因此关闭相应的位。lottabits中其他所有位都保持不变,这是因为对1和任意值执行AND操作时,该位的值将保持不变。
下面是一种更简洁的方法:
如果要确定lottabits中对应于bit的位是否为1,则下面的测试不一定管用:
这是因为即使lottabits中对应的位为1,而其他位也可能为1。仅当对应的位为1,而其他位皆为0时,上述等式才为true。因此修补的方式是,首先对lottabits和bit执行AND操作,这样生成的值的对应位保持不变,因为对1和任何值执行AND操作都将保持该值不变;而其他位都为0,因为对0和任何值执行AND操作的结果都为0。正确的测试如下:
实际应用中,程序员常将上述测试简化为:
因为bit中有一位为1,而其他位都为0,因此lottabits & bit的结果要么为0(测试结果为false),要么为bit(非零值,测试结果为true)。
C++允许定义指向类成员的指针,对这种指针进行声明或解除引用时,需要使用一种特殊的表示法。为说明需要使用的特殊表示法,先来看一个样本类:
如果没有具体的对象,则inches成员只是一个标签。也就是说,这个类将inches定义为一个成员标识符,但要为它分配内存,必须声明这个类的一个对象:
因此,可以结合使用标识符inches和特定的对象,来引用实际的内存单元(对于成员函数,可以省略对象名,但对象被认为是指针指向的对象)。
C++允许这样定义一个指向标识符inches的成员指针:
这种指针与常规指针有所差别。常规指针指向特定的内存单元,而pt指针并不指向特定的内存单元,因为声明中没有指出具体的对象。指针pt指的是inches成员在任意Example对象中的位置。和标识符inches一样,pt被设计为与对象标识符一起使用。实际上,表达式*pt对标识符inches的角色做了假设,因此,可以使用对象标识符来指定要访问的对象,使用pt指针来指定该对象的inches成员。例如,类方法可以使用下面的代码:
其中,和->都是成员解除引用运算符(member dereferencing operator)。声明对象(如obl)后,ob1.pi指的将是ob1对象的inches成员。同样,pq->pt指的是pq指向的对象的inches成员。
改变上述示例中使用的对象,将改变使用的inches成员。不过也可以修改pt指针本身。由于feet的类型与inches相同,因此可以将pt重新设置为指向feet成员(而不指向inches成员),这样ob1.*pt将是ob1的feet成员:
实际上,*pt相当于一个成员名,因此可用于标识(相同类型的)其他成员。
也可以使用成员指针来标识成员函数,其语法稍微复杂点。对于不带任何参数、返回值为void的函数,声明一个指向该函数的指针的代码如下:
声明指向成员函数的指针时,必须指出该函数所属的类。例如,下面的代码声明了一个指向Example类方法的指针:
这表明pf可用于可使用Example方法的地方。注意,Example::*pf必须放在括号中,可以将特定成员函数的地址赋给该指针:
注意,和普通函数指针的赋值情况不同,这里必须使用地址运算符。完成赋值操作后,便可以使用一个对象来调用该成员函数:
必须将ob3.*pf放在括号中,以明确地指出,该表达式表示的是一个函数名。
由于show_feet( )的原型与show_inches( )相同,因此也可以使用pf来访问show_feet( )方法:
程序清单E.1中的类定义包含一个use_ptr( )方法,该方法使用成员指针来访问Example类的数据成员和函数成员。
程序清单E.1 memb_pt.cpp
下面是程序清单E.1中程序的运行情况:
这个例子在编译期间给指针赋值。在更复杂的类中,可以使用指向数据成员和方法的成员指针,以便在运行阶段确定与指针关联的成员。
计算机系统可能限制数据在内存中的存储方式。例如,一个系统可能要求double值存储在编号为偶数的内存单元中,而另一个系统可能要求其起始地址为8个整数倍。运算符alignof将类型作为参数,并返回一个整数,指出要求的对齐方式。例如,对齐要求可能决定结构中信息的组织方式,如程序清单E.2所示。
程序清单E.2 align.cpp
下面是该程序在一个系统中的输出:
两个结构的对齐要求都是8。这意味着结构长度将是8的整数倍,这样创建结构数组时,每个元素的起始位置都是8的整数倍。在程序清单E.2中,每个结构的所有成员只占用13位,但结构要求占用的位数为8的整数倍,这意味着需要填充一些位。在每个结构中,double成员的对齐要求为8的整数倍,但在结构thing1和thing2中,成员的排列顺序不同,这导致thing2需要更多的内部填充,以便其边界处于正确的位置。
关键字noexcept用于指出函数不会引发异常。它也可用作运算符,判断操作数(表达式)是否可能引发异常;如果操作数可能引发异常,则返回false,否则返回true。例如,请看下面的声明:
表达式noexcept(hilt) 的结果为false,因为hilt( ) 的声明未保证不会引发异常,但noexcept(halt) 的结果为true。