类成员指针

Posted xiaojianliu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了类成员指针相关的知识,希望对你有一定的参考价值。

数据成员指针

成员指针是指可以指向非静态成员的指针,成员指针指示的是类的成员,而非类的对象。类的静态成员不属于任何对象,因此无须特殊的指向静态成员指针,指向静态成员的指针与普通的指针没有什么区别。

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

以上是关于类成员指针的主要内容,如果未能解决你的问题,请参考以下文章

通过“指向类成员的指针”访问作为数组的类成员

当前类的成员函数的指针数组

如何删除类内部类类型的类成员指针

通过基类指针C++访问派生类的成员

试图通过指针访问嵌套类的成员函数

在类声明中处理指向成员函数的指针