類成員指針


數據成員指針

成員指針是指可以指向非靜態成員的指針,成員指針指示的是類的成員,而非類的對象。類的靜態成員不屬於任何對象,因此無須特殊的指向靜態成員指針,指向靜態成員的指針與普通的指針沒有什么區別。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM