思维导图

类成员指针 - 图1

1. 简介

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

2. 数据成员指针

2.1 声明数据成员指针

申明成员指针与其他指针类似,不同的是,成员指针还必须包含成员所属的类。
const string Screen:: *pdata;
上诉语句将pdata声明成“一个指向Screen类的const string成员的指针”。

2.2 使用数据成员指针

当我们初始化一个成员指针或为成员指针赋值时,该指针并没有指向任何数据。只有解引用成员指针时我们才提供对象的信息。
有两种成员指针访问运算符:.*->*

  1. #include <iostream>
  2. #include <string>
  3. class Screen
  4. {
  5. public:
  6. typedef std::string::size_type pos;
  7. char get_cursor() const
  8. {
  9. return contents[cursor];
  10. }
  11. char get() const;
  12. char get(pos ht, pos wd) const;
  13. public:
  14. std::string contents;
  15. pos cursor;
  16. pos height;
  17. pos width;
  18. };
  19. int main(int argc, char *argv[])
  20. {
  21. const string Screen:: *pdata1; // 声明一个指向Screen类的const string成员的指针,pdata1只能读取它所指的成员,不能写入,未初始化
  22. Screen::pos Screen:: *pdata2 = nullptr; // 声明一个指向Screen类的Screen::pos成员的指针,同时初始化为nullptr
  23. pdata1 = &Screen::contents; // 对指针赋值
  24. pdata2 = &Screen::cursor; // 对指针赋值
  25. auto pdata3 = &Screen::width; // 初始化指针
  26. // 创建一个类,并进行赋值
  27. Screen sc;
  28. sc.contents = "hello world!";
  29. sc.cursor = 10;
  30. sc.width = 5;
  31. // .*解引用pdata1以获得sc对象的contents成员
  32. auto str = sc.*pdata1; // str = "hello world!"
  33. // ->*解引用pdata2和pdata3以获得psc所指对象的cursor和width成员
  34. Screen *psc = &sc;
  35. Screen::pos val2 = psc->*pdata2; // val2 = 10
  36. auto val3 = psc->*pdata3; // val3 = 5
  37. }

2.3 返回数据成员指针的函数

对于类的成员变量,通常是私有的,所以通常不能直接获得数据成员的指针。如果要访问私有成员,可以定义一个函数,令其返回值是指向该成员的指针:

  1. #include <iostream>
  2. #include <string>
  3. class Screen
  4. {
  5. public:
  6. // 构造函数
  7. Screen():contents("hello world!") {} // 初始化contents
  8. // 返回一个指向Screen类的const string成员的指针
  9. static const std::string Screen::*data()
  10. {
  11. return &Screen::contents;
  12. }
  13. private:
  14. std::string contents;
  15. };
  16. int main(int argc, char *argv[])
  17. {
  18. using std::string;
  19. const string Screen:: *pdata = Screen::data(); // 声明一个指向Screen类的const string成员的指针,初始化为Screen::data()
  20. Screen sc;
  21. auto str = sc.*pdata; // str = "hello world!"
  22. }

3. 成员函数指针

3.1 声明成员函数指针

申明成员函数指针与其他指针类似,不同的是,成员函数指针还必须包含成员所属的类。
int(Screen::*pm)(int, int);
上诉语句将pm声明成“一个指向Screen类有两个int型形参,返回int型值的成员函数指针”。

3.2 使用成员函数指针

与使用指向数据成员的指针一样,使用.*或者->*运算符作用于指向成员函数的指针。
在使用成员函数指针时,可以使用类型别名简化使用。

  1. #include <iostream>
  2. #include <string>
  3. class Screen
  4. {
  5. public:
  6. int getMax(int val1, int val2)
  7. {
  8. return val1 > val2 ? val1 : val2;
  9. }
  10. std::string getMax(const std::string &str1, const std::string &str2)
  11. {
  12. return str1 > str2 ? str1 : str2;
  13. }
  14. int getMin(int val1, int val2)
  15. {
  16. return val1 < val2 ? val1 : val2;
  17. }
  18. using Action = int(Screen::*)(int, int); // 使用类型别名
  19. // 包含4个形参,最后一个形参为Sceen成员函数的指针
  20. Screen *newInstance(Screen *screen, int val1, int val2, Action ac = &Screen::getMin);
  21. };
  22. Screen * Screen::newInstance(Screen *screen, int val1, int val2, Action ac)
  23. {
  24. if (val1 == (this->*ac)(val1, val2))
  25. {
  26. if (screen == nullptr)
  27. {
  28. screen = new Screen();
  29. }
  30. }
  31. return screen;
  32. }
  33. int main(int argc, char *argv[])
  34. {
  35. using std::string;
  36. int (Screen::*pm1)(int, int); // 声明一个成员函数指针,未初始化
  37. pm1 = &Screen::getMax; // 对函数指针进行赋值
  38. string(Screen::*pm2)(const string &, const string &) = &Screen::getMax; // 声明一个成员函数指针,并初始化为Screen::getMax,此处getMax为重载函数,必须显示声明
  39. auto pm3 = &Screen::getMin; // 声明一个成员函数指针,并初始化为Screen::getMin,getMin不为重载函数,可以采用auto
  40. using MinVal = int (Screen::*)(int, int); // 使用类型别名
  41. MinVal minVal = &Screen::getMin; // minVal指向Screen的getMin成员函数
  42. Screen screen; // 定义一个类
  43. Screen *pscreen = &screen; // 定义一个类指针
  44. // 注意此处(screen.*pm1)的括号必不可少,因为调用运算符的优先级要高于指针指向成员运算符的优先级
  45. int maxVal1 = (screen.*pm1)(2, 3); // 调用成员函数指针,maxVal1 = 3
  46. int maxVal2 = (pscreen->*pm1)(2, 3); // 调用成员函数指针,maxVal2 = 3
  47. int minVal1 = (screen.*pm3)(2, 3); // 调用成员函数指针,minVal1 = 2
  48. int minVal2 = (screen.*minVal)(2, 3); // 调用成员函数指针,minVal2 = 2
  49. string maxStr1 = (screen.*pm2)("abc", "zxc"); // 调用函数指针,maxStr1 = "zxc"
  50. string maxStr2 = (pscreen->*pm2)("abc", "zxc"); // 调用函数指针,maxStr2 = "zxc"
  51. Screen *pscreen1 = screen.newInstance(nullptr, 2, 3); // 使用默认实参
  52. Screen *pscreen2 = screen.newInstance(nullptr, 2, 3, minVal); // 使用之前定义的变量minVal
  53. Screen *pscreen3 = screen.newInstance(nullptr, 3, 2, &Screen::getMin); // 显式的传入地址,pscreen3 = nullptr
  54. }

注意:因为函数调用运算符的优先级高于指针指向成员运算符的优先级,所以在声明指向成员函数的指针并使用这样的指针进行函数调用时,括号必不可少:(C::*p)(parms)(obj.*p)(args)

3.3 成员指针函数表

成员指针函数表是一种常用的用法,即将一个类中几个相同类型的成员函数存入一个函数表(如:采用vector)中,然后采用表索引进行访问使用函数指针。

  1. #include <iostream>
  2. #include <string>
  3. class Screen
  4. {
  5. public:
  6. enum Directions
  7. {
  8. E_HOME = 0,
  9. E_FOREARD,
  10. E_BACK,
  11. E_UP,
  12. E_DOWN,
  13. };
  14. public:
  15. using Action = Screen & (Screen::*)();
  16. Screen &move(Directions dc)
  17. {
  18. return (this->*m_menu[dc])(); // 注意此处的括号
  19. }
  20. private:
  21. static Action m_menu[];
  22. Screen &home() { return *this; }
  23. Screen &forward() { return *this; }
  24. Screen &back() { return *this; }
  25. Screen &up() { return *this; }
  26. Screen &down() { return *this; }
  27. };
  28. Screen::Action Screen::m_menu[] = {
  29. &Screen::home,
  30. &Screen::forward,
  31. &Screen::back,
  32. &Screen::up,
  33. &Screen::down,
  34. };
  35. int main(int argc, char *argv[])
  36. {
  37. Screen screen;
  38. Screen &sc = screen.move(Screen::E_BACK);
  39. return 0;
  40. }

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_fnfunction一样,可以从成员函数指针生成一个可调用对象,但是其与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版)》。