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();