數據成員指針
成員指針是指可以指向非靜態成員的指針,成員指針指示的是類的成員,而非類的對象。類的靜態成員不屬於任何對象,因此無須特殊的指向靜態成員指針,指向靜態成員的指針與普通的指針沒有什么區別。
class Screen
{
public:
typedef std::string::size_type pos;
char get_cursor() const { return contents[cursor]; }
char get() const;
char get(pos gt, pos wd) const;
private:
std::string contents;
pos cursor;
pos height, width;
};
聲明數據成員指針:
//@ pdata 可以指向一個常量(非常量) Screen 對象的 string 成員
const string Screen::*pdata;
常量對象的數據成員本身也是常量,因此將指針聲明成指向 const string 成員的指針意味着 pdada 可以指向任何 Screen 對象的一個成員,而不管該 Screen 對象是否是常量。作為交換條件,只能使用 pdata 讀取它所指的成員,而不能向它寫入內容。
初始化陳冠指針或者向它賦值時,需指定它所指的成員:
pdata = &Screen::contents;
在 C++ 11中可以使用 auto 或者 decltype:
auto pdata = &Screen::contents;
使用數據成員指針
初始化一個成員指針或者尾為成員指針賦值時,該指針並沒有指向任何數據。成員指針指定了成員而非該成員所屬的對象,只有當解引用成員指針時才能提供對象的信息。
成員指針訪問運算符:.*
和 ->*
:
Screen myScreen, *pScreen = &myScreen;
//@ .* 解引用 pdata 以獲得 myScreen 對象的 contents 成員
auto s = myScreen.*pdata;
//@ ->* 解引用 pdata 以獲得 pScreen 所指對象的 contents 成員
s = pScreen->*pdata;
返回數據成員指針的函數
常規的訪問控制規則對成員指針同樣有效,例如 Screen 的 contents 成員是私有的,因此之前對於 pdata 的使用必須位於 Screen 類的成員或友元內部,否則程序將引發錯誤。
最好定義一個函數,令其返回值是指向該成員的指針:
class Screen
{
public:
//@ data 是一個靜態成員,返回一個成員指針
static const std::string Screen::*data()
{
return &Screen::contents;
}
};
從右向左閱讀可知 data 函數返回的是一個指針,該指針指向 Screen 類的 const string 成員。
const string Screen::*pdata = Screen::data();
成員函數指針
//@ pmf 是一個指針,它可以指向 Screen 的某個常量成員函數
//@ 前提是該函數不接受任何實參,並且返回一個 char
auto pmf = &Screen::get_cursor;
如果成員函數是 const 成員或者引用成員,則必須將 const 限定符或引用包含進來。
和普通的函數指針類似,如果成員存在重載的問題,則必須顯式地聲明函數類型以明確指出想要使用哪個函數:
char (Screen::*pmf2)(Screen::pos, Screen::pos) const;
pmf2 = &Screen::get;
上面 Screen::*pmf2
的括號是必不可少的,如果聲明成:
//@ 錯誤,非成員函數不能使用 const 限定符
char Screen::*p(Screen::pos, Screen::pos) const;
上面的聲明試圖定義一個名為 p 的普通函數,並且返回 Screen 類的一個 char 成員。因為聲明的是普通函數,所以不能使用 const 限定符。
和普通函數指針不同,在成員函數和指向該成員的指針之間不存在自動轉換規則:
//@ pmf 指向一個 Screen 成員,該成員不接受任何實參並且返回類型是 char
pmf = &Screen::get; //@ 必須顯式地使用取地址運算符
pmf = Screen::get; //@ 錯誤,在成員函數和指針之間不存在自動轉換規則
使用成員函數指針
使用 .*
和 ->*
運算符作用於指向成員函數的指針,以調用類的成員函數:
Screen myScreen, *pScreen = &myScreen;
//@ 通過 pScreen 所指的對象調用 pmf 所指的函數
char c1 = (pScreen->*pmf)();
//@ 通過 myScreen 對象將實參 0,0 傳遞給含有兩個形參的 get 函數
char c2 = (myScreen.*pmf2)(0,0);
上面的代碼如果不加括號:
myScreen.*pmf();
則等價於:
myScreen.*(pmf());
含義是調用一個名為 pmf 的函數,然后使用該函數的返回值作為指針指向成員運算符(.*
) 的運算對象,但是 pmf 並不是一個函數,因此代碼將出錯。
使用成員指針的類型別名
//@ Action 是一個指向 Screen 成員函數的指針,它接受兩個 pos 實參,並返回一個 char
using Action = char(Screen::*)(Screen::pos, Screen::po) const;
通過使用 Action 可以簡化指向 get 的指針定義:
Action get = &Screen::get; //@ get 指向 Screen 的 get 成員
可以將指向成員函數的指針作為某個函數的返回類型或形參類型,其中,指向成員的指針形參也可以擁有默認實參:
//@ action 接受一個 Screen 的引用,和一個指向 Screen 成員函數的指針
Screen& action(Screen&, Action = &Action::get);
調用 action 函數:
Screen myScreen;
//@ 等價的調用
action(myScreen); //@ 使用默認參數
action(myScreen,get); //@ 使用之前定義的變量 get
action(myScreen,&Screen::get); //@ 顯式地傳入地址
成員指針函數表
對於普通函數指針和指向成員函數的指針來說,一種常見的用法是將其存入一個函數表當中。如果一個類含有幾個相同類型的成員,則這樣一張表可以幫助從這些成員中選擇一個。假定 Screen 類含有幾個成員函數,每個函數負責將光標向指定的方向移動:
class Screen
{
public:
Screen& home();
Screen& forward();
Screen& back();
Screen& up();
Screen& down();
};
我們定義一個 move 函數,使其可以調用上面任意 一個函數:
class Screen
{
public:
using Action = Screen& (Screen::*)();
enum Directions {HOME,FORWARD,BACK,UP,DOWN};
Screen& move(Directions);
private:
static Action Menu[]; //@ 函數表
};
數組 Menu 依次保存每個光標移動函數的指針,這些函數將按照 Directions 中枚舉對應的偏移量存儲:
Screen& Screen::move(Directions cm)
{
return (this->*Menu[cm])(); //@ Menu[cm] 指向一個成員函數
}
move 中函數調用的原理是:首先獲取索引值為 cm 的 Menu 元素,該元素是指向 Screen 成員函數指針根據 this 所指的對象調用該元素所指的成員函數。
Screen& Screen::move(Directions cm)
{
return (this->*Menu[cm])(); //@ Menu[cm] 指向一個成員函數
}
Screen myScreen;
myScreen.move(Screen::HOME);
myScreen.move(Screen::DOWN);
Screen::Action Screen::Menu[] = {
&Screen::home,
&Screen::forward,
&Screen::back,
&Screen::up,
&Screen::down,
};
將成員函數用作可調用對象
要想通過一個指針成員函數的指針進行函數調用,必須首先利用 .*
運算符或 ->*
運算符將該指針綁定到特定的對象上。
成員指針不是一個可調用對象,這樣的指針不支持函數調用運算符。因為成員指針不是可調用對象,所以不能直接將一個指向成員函數的指針傳遞給算法:
例如,想在一個 string 的 vector 中找到第一個空的 string:
auto fp = &string::empty; //@ fp 指向 string 的 empty 函數
//@ error,必須使用 .* 或 ->* 調用成員指針
find_if(svec.begin(), svec.end(), fp);
find_if 算法需要一個可調用對象,fp 是一個成員函數指針,非可調用對象。
使用 function 生成一個可調用對象
從指向成員函數指針獲取可調用對象的一種方法是使用標准庫模板 function :
function<bool(const string&)> fcn = &Screen::empty;
find_if(svec.begin(), svec.end(), fcn);
提供給 function 的形式必須指明對象是否是以指針或引用的形式傳入:
vector<string*> pvec;
function<bool(const string*)> fp = &Screen::empty;
//@ fp 接受一個指向 string 的指針,然后使用 ->* 調用 empty
find_if(pvec.begin(), pvec.end(), fp);
使用 mem_fn 生成一個可調用對象
通過使用標准庫功能 mem_fn 來讓編譯器負責推斷成員類型。mem_fn 也定義在 function 頭文件中,並且可以從成員指針生成一個可調用對象:和 function 不同的是,mem_fn 可以根據成員指針的類型推斷可調用對象的類型,而無須顯式地指定:
find_if(svec.begin(), svec.end(), mem_fn(&string::empty));
使用 mem_fn(&string::empty)
生成一個可調用對象,該對象接受一個 string 實參,返回一個 bool 值。
mem_fn 生成的可調用對象可以通過對象調用,也可以通過指針調用:
auto f = mem_fn(&string::empty); //@ f 接受一個 string 或者 string*
f(*svec.begin()); //@ ok,傳入一個 string 對象,f 使用 .* 調用 empty
f(&svec[0]); //@ ok,傳入一個 string 的指針,f 使用 ->* 調用 empty
實際上可以認為 mem_fn 生成的可調用對象含有一對重載的函數調用運算符:
- 一個接受 string*。
- 一個接受 string&。
使用 bind 生成有一個可調用對象
可以使用 bind 從成員安徽省農戶生成一個可調用對象:
//@ 選擇范圍中的每個 string,並將其 bind 到 empty 的第一個隱式實參上
auto it = find_if(svec.begin(), svec.end(), bind(&string::empty, _1));
和 function 類似的地方,使用 bind 函數時,必須將函數中用於表示執行對象的隱式形參轉換成顯示的。
和 mem_fn 類似的地方,bind 生成的可調用對象的第一個實參既可以是 string 的指針,也可以是 string 的引用:
auto f = bind(&string::empty, _1);
f(*svec.begin()); //@ ok,傳入一個 string 對象,f 使用 .* 調用 empty
f(&svec[0]); //@ ok,傳入一個 string 的指針,f 使用 ->* 調用 empty