思维导图
1. 简介
成员指针(pointer to member):是指可以指向类的非静态成员的指针。成员指针指示的是类的成员,而非类的对象。类的静态成员不属于任何对象,因此无须特殊的指向静态成员的指针,指向静态成员的指针与普通指针没有什么区别。
2. 数据成员指针
2.1 声明数据成员指针
申明成员指针与其他指针类似,不同的是,成员指针还必须包含成员所属的类。const string Screen:: *pdata;
上诉语句将pdata
声明成“一个指向Screen
类的const string
成员的指针”。
2.2 使用数据成员指针
当我们初始化一个成员指针或为成员指针赋值时,该指针并没有指向任何数据。只有解引用成员指针时我们才提供对象的信息。
有两种成员指针访问运算符:.*
和->*
。
#include <iostream>
#include <string>
class Screen
{
public:
typedef std::string::size_type pos;
char get_cursor() const
{
return contents[cursor];
}
char get() const;
char get(pos ht, pos wd) const;
public:
std::string contents;
pos cursor;
pos height;
pos width;
};
int main(int argc, char *argv[])
{
const string Screen:: *pdata1; // 声明一个指向Screen类的const string成员的指针,pdata1只能读取它所指的成员,不能写入,未初始化
Screen::pos Screen:: *pdata2 = nullptr; // 声明一个指向Screen类的Screen::pos成员的指针,同时初始化为nullptr
pdata1 = &Screen::contents; // 对指针赋值
pdata2 = &Screen::cursor; // 对指针赋值
auto pdata3 = &Screen::width; // 初始化指针
// 创建一个类,并进行赋值
Screen sc;
sc.contents = "hello world!";
sc.cursor = 10;
sc.width = 5;
// .*解引用pdata1以获得sc对象的contents成员
auto str = sc.*pdata1; // str = "hello world!"
// ->*解引用pdata2和pdata3以获得psc所指对象的cursor和width成员
Screen *psc = ≻
Screen::pos val2 = psc->*pdata2; // val2 = 10
auto val3 = psc->*pdata3; // val3 = 5
}
2.3 返回数据成员指针的函数
对于类的成员变量,通常是私有的,所以通常不能直接获得数据成员的指针。如果要访问私有成员,可以定义一个函数,令其返回值是指向该成员的指针:
#include <iostream>
#include <string>
class Screen
{
public:
// 构造函数
Screen():contents("hello world!") {} // 初始化contents
// 返回一个指向Screen类的const string成员的指针
static const std::string Screen::*data()
{
return &Screen::contents;
}
private:
std::string contents;
};
int main(int argc, char *argv[])
{
using std::string;
const string Screen:: *pdata = Screen::data(); // 声明一个指向Screen类的const string成员的指针,初始化为Screen::data()
Screen sc;
auto str = sc.*pdata; // str = "hello world!"
}
3. 成员函数指针
3.1 声明成员函数指针
申明成员函数指针与其他指针类似,不同的是,成员函数指针还必须包含成员所属的类。int(Screen::*pm)(int, int);
上诉语句将pm
声明成“一个指向Screen
类有两个int
型形参,返回int
型值的成员函数指针”。
3.2 使用成员函数指针
与使用指向数据成员的指针一样,使用.*
或者->*
运算符作用于指向成员函数的指针。
在使用成员函数指针时,可以使用类型别名简化使用。
#include <iostream>
#include <string>
class Screen
{
public:
int getMax(int val1, int val2)
{
return val1 > val2 ? val1 : val2;
}
std::string getMax(const std::string &str1, const std::string &str2)
{
return str1 > str2 ? str1 : str2;
}
int getMin(int val1, int val2)
{
return val1 < val2 ? val1 : val2;
}
using Action = int(Screen::*)(int, int); // 使用类型别名
// 包含4个形参,最后一个形参为Sceen成员函数的指针
Screen *newInstance(Screen *screen, int val1, int val2, Action ac = &Screen::getMin);
};
Screen * Screen::newInstance(Screen *screen, int val1, int val2, Action ac)
{
if (val1 == (this->*ac)(val1, val2))
{
if (screen == nullptr)
{
screen = new Screen();
}
}
return screen;
}
int main(int argc, char *argv[])
{
using std::string;
int (Screen::*pm1)(int, int); // 声明一个成员函数指针,未初始化
pm1 = &Screen::getMax; // 对函数指针进行赋值
string(Screen::*pm2)(const string &, const string &) = &Screen::getMax; // 声明一个成员函数指针,并初始化为Screen::getMax,此处getMax为重载函数,必须显示声明
auto pm3 = &Screen::getMin; // 声明一个成员函数指针,并初始化为Screen::getMin,getMin不为重载函数,可以采用auto
using MinVal = int (Screen::*)(int, int); // 使用类型别名
MinVal minVal = &Screen::getMin; // minVal指向Screen的getMin成员函数
Screen screen; // 定义一个类
Screen *pscreen = &screen; // 定义一个类指针
// 注意此处(screen.*pm1)的括号必不可少,因为调用运算符的优先级要高于指针指向成员运算符的优先级
int maxVal1 = (screen.*pm1)(2, 3); // 调用成员函数指针,maxVal1 = 3
int maxVal2 = (pscreen->*pm1)(2, 3); // 调用成员函数指针,maxVal2 = 3
int minVal1 = (screen.*pm3)(2, 3); // 调用成员函数指针,minVal1 = 2
int minVal2 = (screen.*minVal)(2, 3); // 调用成员函数指针,minVal2 = 2
string maxStr1 = (screen.*pm2)("abc", "zxc"); // 调用函数指针,maxStr1 = "zxc"
string maxStr2 = (pscreen->*pm2)("abc", "zxc"); // 调用函数指针,maxStr2 = "zxc"
Screen *pscreen1 = screen.newInstance(nullptr, 2, 3); // 使用默认实参
Screen *pscreen2 = screen.newInstance(nullptr, 2, 3, minVal); // 使用之前定义的变量minVal
Screen *pscreen3 = screen.newInstance(nullptr, 3, 2, &Screen::getMin); // 显式的传入地址,pscreen3 = nullptr
}
注意:因为函数调用运算符的优先级高于指针指向成员运算符的优先级,所以在声明指向成员函数的指针并使用这样的指针进行函数调用时,括号必不可少:(C::*p)(parms)
和(obj.*p)(args)
。
3.3 成员指针函数表
成员指针函数表是一种常用的用法,即将一个类中几个相同类型的成员函数存入一个函数表(如:采用vector)中,然后采用表索引进行访问使用函数指针。
#include <iostream>
#include <string>
class Screen
{
public:
enum Directions
{
E_HOME = 0,
E_FOREARD,
E_BACK,
E_UP,
E_DOWN,
};
public:
using Action = Screen & (Screen::*)();
Screen &move(Directions dc)
{
return (this->*m_menu[dc])(); // 注意此处的括号
}
private:
static Action m_menu[];
Screen &home() { return *this; }
Screen &forward() { return *this; }
Screen &back() { return *this; }
Screen &up() { return *this; }
Screen &down() { return *this; }
};
Screen::Action Screen::m_menu[] = {
&Screen::home,
&Screen::forward,
&Screen::back,
&Screen::up,
&Screen::down,
};
int main(int argc, char *argv[])
{
Screen screen;
Screen &sc = screen.move(Screen::E_BACK);
return 0;
}
4. 将成员函数用作可调用对象
成员函数的指针必须使用.*
运算符或->*
运算符将其绑定到特定的对象上,与普通函数指针不同,成员函数指针不是一个可调用对象,这样的指针不支持函数调用运算符。
例如:
vector<string> vec;
auto fp = &string::empty; // fp指向string的empty函数
// 错误,必须使用.*或->*调用成员指针
find_if(vec.begin(), vec.end(), fp);
因为在find_if
的内部将执行如下形式的代码,导致无法通过编译。
// 检查当前元素的断言是否为真
if (fp(*it)) // 错误:要想通过成员函数指针调用函数,必须使用->*运算符
4.1 使用function
定义格式:
vector<string> vec;
function<bool (const string &)> fcn = &string::empty; // fcn接受一个指向string的引用,然后使用.*调用empty
find_if(vec.begin(), vec.end(), fcn);
// 或者
vector<string *> vecp;
function<bool (const string *)> fp = &string::empty; // fp接受一个指向string的指针,然后使用->*调用emmty
auto it= find_if(vecp.begin(), vecp.end(), fcn);
上述代码表示告诉function
一个事实,即empty
是一个接受string
(或者string *
)参数并返回bool
值的函数。
通常情况下,执行成员函数的对象将被传给隐式的this
形参,但是使用function
为成员函数生成一个可调用对象时,必须先“翻译”该代码,使得隐式的形参变成显示的。
定义function对象时,必须指定对象所能表示的函数类型,即可调用对象的形式。如果可调用对象是一个成员函数,则第一个形参必须表示该成员是在哪个(一般是隐式的)对象(如:const string &
)上执行的。同时,我们提供给function的形式中,还必须指明对象是否以指针或引用的形式传入的。
4.2 使用mem_fn
定义格式:
vector<string> vec;
auto it = find_if(vec.begin(), vec.end(), mem_fn(&string::empty));
auto f = mem_fn(&string::empty); // f接受一个string或者一个string*
f(*vec.begin()); // 正确:传入一个string对象,f使用.*调用empty
f(&vec[0]); // 正确:传入一个string的指针,f使用->*调用empty
mem_fn
与function
一样,可以从成员函数指针生成一个可调用对象,但是其与function
存在一些不同,mem_fn
可以根据成员指针的类型推断可调用对象的类型,而无需用户显式地指定。mem_fn
生成的可调用对象可以通过对象调用,也可以通过指针调用。
4.3 使用bind
定义格式:
vector<string> vec;
auto it = find_if(vec.begin(), vec.end(), bind(&string::empty, std::placeholders::_1));
auto f = bind(&string::empty, std::placeholders::_1); // f接受一个string或者一个string*
f(*vec.begin()); // 正确:传入一个string对象,f使用.*调用empty
f(&vec[0]); // 正确:传入一个string的指针,f使用->*调用empty
bind
在使用时,必须将函数中用于表示执行对象的隐式形参转换成显示的。bind
生成的可调用对象可以通过对象调用,也可以通过指针调用。
参考:
1)《C++ Primer 中文版(第5版)》。