C++ 与“类”有关的注意事项总结(五):指向类成员的指针
时间:2011-04-05 来源:charley_yang
假定 Screen类定义了四个新成员函数——forward() back() up()和down() 它们分别向右、向左、向上和向下移动光标。首先,我们在类体中声明这些新的成员函数 :
class Screen {
public:
inline Screen& forward();
inline Screen& back();
inline Screen& end();
inline Screen& up();
inline Screen& down();
// 其他成员函数同前
private:
inline int row();
// 其他私有成员同前
};
Screen类的用户要求一个函数 repeat(),它执行用户指定的操作 n 次,它的非通用的实现如下:
Screen &repeat( char op, int times )
{
switch( op ) {
case DOWN: // 调用 Screen::down() n 次
break;
case UP: // 调用 Screen::up() n 次
break;
// ...
}
}
虽然这种实现能够工作,但是它有许多缺点 一个问题是,它依赖于 Screen的成员函数保持不变,每次增加或删减一个成员函数,都必须更新 repeat(); 第二个问题是它的大小,由于必须测试每个可能的成员函数,所以repeat()的完整列表非常大而且不必要地复杂 。
替换的办法是一种更通用的实现。用Screen成员函数的指针类型的参数取代 OP,repeat() 不需要再确定用户期望什么样的操作,整个switch 语句也可以被去掉。
一、类成员的类型
函数指针不能被赋值为成员函数的地址,即使返回类型和参数表完全匹配。例如 ,下面的 pfi 是一个函数指针,该函数没有参数,返回类型为 int:
int (*pfi) ();
给出两个全局函数 HeightIs()和 WidthIs() :
int HeightIs();
int WidthIs();
把 HeightIs()和WidthIs()中的任何一个或两个赋值给指针 pfi 都是合法且正确的:
pfi = HeightIs;
pfi = WidthIs;
类 Screen也定义了两个访问函数——height()和 width()——它们也没有参数,返回类型也 是 int :
inline int Screen::height() { return _height; }
inline int Screen::width() { return _width; }
但是把 height()或 width()任何一个赋给指针 pfi 都是类型违例,都将导致编译时刻错误:
// 非法赋值: 类型违例
pfi = &Screen::height;
为什么会出现类型违例呢?成员函数有一个非成员函数不具有的属性——它的类 (its class), 指向成员函数的指针必须与向其赋值的函数类型匹配,不是两个而是三个方面都要匹配 :(1) 参数的类型和个数 (2) 返回类型 (3) 它所属的类类型 。
成员函数指针首先必须被绑定在一个对象或者一个指针上,才能得到被调用对象的 this指针,然后才调用指针所指的成员函数。虽然普通函数指针和成员函数指针都被称作指针,但是它们是不同的事物。
成员函数指针的声明要求扩展的语法,它要考虑类的类型,对指向类数据成员的指针也是这样。考虑 Screen类的成员 height 的类型,它的完整类型是“ short 型的 Screen类的成 员“,指向_height 的指针的完整类型是 ”指向short 型的 Screen类的成员“的指针,这可以写为 :
short Screen::*
指向 short 型的 Screen类的成员的指针的定义如下 :
short Screen::*ps_Screen;
ps_Screen可以用_height 的地址初始化,如下:
short Screen::*ps_Screen = &Screen::_height;
类似地,它也可以用_width的地址赋值 如下
ps_Screen = &Screen::_width;
定义一个成员函数指针需要指定函数返回类型、参数表和类,例如,指向Screen成员函数并且能够引用成员函数 height()和 width()的指针类型如下:
int (Screen::*) ()
这种类型指定了一个指向类 Screen的成员函数的指针,它没有参数,返回值类型为int 。指向成员函数的指针可被声明、初始化及赋值如下:
// 所有指向类成员的指针都可以用 0 赋值
int (Screen::*pmf1)() = 0;
int (Screen::*pmf2)() = &Screen::height;
pmf1 = pmf2;
pmf2 = &Screen::width;
使用 typedef可以使成员指针的语法更易读,例如,下面的类型定义:
Screen& ( Screen::* ) ()
也就是指向 Screen类成员函数的一个指针,该函数没有参数,返回Screen类对象的引用。它可以被下列 typedef定义 Action所取代:
typedef Screen& (Screen::*Action)();
Action default = &Screen::home;
Action next = &Screen::forward;
指向成员函数类型的指针可以被用来声明函数参数和函数返回类型 ,我们也可以为成员函数类型的参数指定缺省实参,例如:
Screen& action( Screen&, Action );
action()被声明为有两个参数 ,一个 Screen类对象的引用 和一个指向类 Screen的成员函数的指针,该函数没有参数,返回Screen类对象的引用。 action()可以以下列任意方式被调用:
Screen myScreen;
typedef Screen& (Screen::*Action)();
Action default = &Screen::home;
extern Screen& action( Screen&, Action = &Screen::display );
void ff()
{
action( myScreen );
action( myScreen, default );
action( myScreen, &Screen::end );
}
二、使用指向类成员的指针
类成员的指针必须总是通过特定的对象或指向该类类型的对象的指针来访问 ,我们通过使用两个指向成员操作符的指针(针对类对象和引用的.* 以及针对指向类对象的指针的->*)来做到这一点,例如,如下所示, 我们通过成员函数的指针调用成员函数:
int (Screen::*pmfi)() = &Screen::height;
Screen& (Screen::*pmfS)( const Screen& ) = &Screen::copy;
Screen myScreen, *bufScreen;
// 直接调用成员函数
if ( myScreen.height() == bufScreen->height() )
bufScreen->copy( myScreen );
// 通过成员指针的等价调用
if ( (myScreen.*pmfi)() == (bufScreen->*pmfi)() )
(bufScreen->*pmfS)( myScreen );
如下调用:
(myScreen.*pmfi)()
(bufScreen->*pmfi)()
要求有括号 因为调用操作符——()——的优先级高于成员操作符指针的优先级,没有括号
myScreen.*pmfi()
将会被解释为
myScreen.*(pmfi())
它会先调用函数 pmfi() 把它的返回值与成员对象操作符的指针 .* 绑定,当然 pmfi的类型不支持这种用法,会产生一个编译时刻错误。
类似地 指向数据成员的指针可以按下列方式被访问:
typedef short Screen::*ps_Screen;
Screen myScreen, *tmpScreen = new Screen( 10, 10 );
ps_Screen pH = &Screen::_height;
ps_Screen pW = &Screen::_width;
tmpScreen->*pH = myScreen.*pH;
tmpScreen->*pW = myScreen.*pW;
下面是在开始时讨论的成员函数 repeat()的实现,它被修改为用一个成员函数的指针作为参数:
typedef Screen& (Screen::*Action)();
Screen& Screen::repeat( Action op, int time s )
{
for ( int i = 0; i < times; ++i )
(this->*op)();
return *this;
}
参数 op 是一个成员函数的指针 它指向要被调用times 次的成员函数,若要为 repeat() 的参数提供缺省实参,则声明如下:
class Screen {
public:
Screen &repeat( Action = &Screen::forward, int = 1 );
// ...
};
repeat()的调用如下 :
Screen myScreen;
myScreen.repeat(); // repeat( &Screen::forward, 1 );
myScreen.repeat( &Screen::down, 20 );
三、静态类成员的指针
在非静态类成员的指针和静态类成员的指引之间有一个区别,指向类成员的指针语法不能被用来引用类的静态成员,静态类成员是属于该类的全局对象和函数,它们的指针是普通指针。(请记住静态成员函数没有 this指针)
指向静态类成员的指针的声明看起来与非类成员的指针相同,解引用该指针不需要类对象,例如 我们来看一下类 Account:
class Account {
public:
static void raiseInterest( double incr );
static double interest() { return _interestRate; }
double amount() { return _amount; }
private:
static double _interestRate;
double _amount;
string _owner;
};
inline void Account::raiseInterest( double incr )
{
_interestRate += incr;
}
&_interestRate的类型是double* 而不是 :
// 不是 &_interestRate 的类型
double Account::*
指向_interestRate的指针定义如下:
// OK: 是 double*, 而不是 double Account::*
double *pd = &Account::_interestRate;
它被解引用的方式与普通指针一样,不需要相关的类对象,例如:
Account unit;
// 用普通的解引用操作符
double daily = *pd /365 * unit._amount;
但是,因为_interestRate和_amount 都是私有成员,所以我们需要使用静态成员函数interest()和非静态成员函数amount()。
指向interest()的指针的类型是一个普通函数指针
// 正确
double (*) ()
而不是类 Account 的成员函数的指针
// 不正确
double (Account::*) ()
这个指针的定义和对 interest()的间接调用处理方式与非类的指针相同:
// ok: douple(*pf) () 不是 double(Account::*pf)()
double (*pf)() = &Account::interest;
double daily = pf () / 365 * unit.amount();