数据成员指针
成员指针是指可以指向非静态成员的指针,成员指针指示的是类的成员,而非类的对象。类的静态成员不属于任何对象,因此无须特殊的指向静态成员指针,指向静态成员的指针与普通的指针没有什么区别。
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