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