11.4 指向成员的指针
指针是指向一些内存地址的变量,既可以是数据的地址也可以是函数的地址。所以,可以在运行时改变指针指向的内容。C++的成员指针(pointer-to-member)遵从同样的概念,除了所选择的内容是在类中之内的成员指针。这里麻烦的是所有的指针需要地址,但在类内部是没有地址的;选择一个类的成员意味着在类中偏移。只有把这个偏移和具体对象的开始地址结合,才能得到实际地址。成员指针的语法要求选择一个对象的同时间接引用成员指针。
为了理解这个语法,先来考虑一个简单的结构:如果有一个这样结构的指针sp和对象so,可以通过下面方法选择成员:
现在,假设有一个普通的指向integer的指针ip。为了取得ip指向的内容,用一个*号间接引用这个指针。
最后,考虑如果有一个指向一个类对象成员的指针,如果假设它代表对象内一定的偏移,将会发生什么?为了取得指针指向的内容,必须用号间接引用。但是,它只是一个对象内的偏移,所以必须也要指定那个对象。因此,号要和间接引用的对象结合。所以,对于指向一个对象的指针,新的语法变为->,对于一个对象或引用,则为.,如下所示。
现在,让我们看看定义pointerToMember的语法是什么?其实它像任何一个指针,必须说出它指向什么类型。并且,在定义中也要使用一个‘*’号。惟一的区别只是它必须说出这个成员指针使用什么类的对象。当然,这是用类名和作用域运算符实现的。因此,可表示如下:
定义一个名字为pointerToMember的成员指针,该指针可以指向在ObjectClass类中的任一int类型的成员。还可以在定义的时候初始化这个成员指针。
因为仅仅提到了一个类而非那个类的对象,所以没有ObjectClass:a的确切“地址”。因而,&ObjectClass:a仅是作为成员指针的语法被使用。
下面例子说明了如何建立和使用指向数据成员的指针:
显然,除了对于一些特例(即需要精确地指向的),这里就显得有些过于难用而无法随处使用。
另外,成员指针是受限制的,它们仅能被指定给在类中的确定的位置。例如,我们不能像使用普通指针那样增加或比较成员指针。
11.4.1 函数
一个类似的练习产生指向成员函数的指针语法。指向函数的指针(参见第3章的最后部分)定义如下:
(fp)的圆括号用来迫使编译器正确判断定义。没有圆括号,这个表达式就是一个返回int值的函数。
为了定义和使用成员函数的指针,圆括号扮演同样重要的角色。假设在一个结构内有一个函数,通过给普通函数插入类名和作用域运算符就可以定义一个指向成员函数的指针。
从对fp2定义可以看出,一个成员指针可以在它创建的时候被初始化,或者也可在其他任何时候。不像非成员函数,当获取成员函数的地址时,符号&不是可选的。但是,可以给出不含参数列表的函数标识符,因为重载方案可以由成员指针的类型所决定。
11.4.1.1 一个例子
在程序运行时,我们可以改变指针所指的内容。因此在运行时就可以通过指针选择或改变我们的行为,这就为程序设计提供了重要的灵活性。成员指针也一样,它允许在运行时选择一个成员。特别的,当类只有公有成员函数(数据成员通常被认为是内部实现的一部分)时,就可以用指针在运行时选择成员函数,下面的例子正是这样:
当然,期望一般用户创建如此复杂的表达式不是很合乎情理的。如果用户必须直接操作成员指针,那么typedef是适合的。为了安排得当,可以使用成员指针作为内部执行机制的一部分。现在回到先前的那个在类中使用成员指针的例子上来。用户所要做的是传递一个数字以选择一个函数[1]。
在类接口和main()函数里,可以看到,包括函数本身在内的整个实现被隐藏了。代码甚至必须请求对函数的Count()。用这个方法,类执行者可以在内部执行时改变函数的数量而不影响使用这个类的代码。
在构造函数中,成员指针的初始化似乎过分指定了。是否可以这样写:
因为名字g在成员函数中出现,这是否可以自动地认为在这个类范围内呢?问题是这不符合成员函数的语法,它的语法要求每个人尤其编译器能够判断将要进行什么。相似地,当成员指针被间接引用时,它看起来像这样:
它仍是过分指定的,this似乎多余。正如前面所讲的,当它被间接引用时,语法也需要成员指针总是和一个对象绑定在一起。
[1]感谢Owen Mortensen提供了本例。