定义

指向类非静态static成员的指针。普通的指针的,都是指向类对象的指针。
静态成员不属于类,指向静态成员的指针就是普通指针。
类成员指针类型包含两部分:类类型和成员类型,即指向哪个类的哪个成员。
由于成员类型的不同,可以分为:

  • 数据成员指针,指向数据成员。
  • 成员函数指针,指向函数成员。

和普通的指针一样看待,可以作为函数返回值或者形参

数据成员指针

  1. class T {
  2. private:
  3. int fuck;
  4. int shit;
  5. };
  6. int main(){
  7. // ****************************************************
  8. // 声明方式(数据成员指针)
  9. // ****************************************************
  10. // 声明方式一
  11. int T::*pData; // pData是指向类T的int类型成员的指针。
  12. pData = &T::fuck; // pData指向fuck。
  13. // 声明方式二,C++新标准
  14. auto pData = &T::fuck; // 同上
  15. // 声明方式三
  16. decltype(&T::fuck) pData = &T::fuck; // 同上
  17. // 声明方式四,using或者typedef
  18. using MemPtr = int T::*; // 类型别名,类T的int型成员
  19. MemPtr pData = &T::fuck; // 同上
  20. // ****************************************************
  21. // 使用方式(数据成员指针),获得类成员,进而获得对象的该成员。
  22. // ****************************************************
  23. T t, *pt = &t;
  24. auto s = t.*pData; // *pData解引用获得类成员,然后通过对象访问t.,获得对象的该数据成员
  25. s = pt->*pData; // 指针形式。
  26. }

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

  1. class T {
  2. private:
  3. int fuck;
  4. int shit;
  5. };
  6. int T::* fuck(){ // 返回数据成员指针,指向类T的int类型成员。
  7. return &T::fuck; // 返回fuck成员
  8. }
  9. int T::*shit(){ // 同上,别搞错了。
  10. return &T::shit; // 返回shit成员
  11. }
  12. int main(){
  13. auto p1 = fuck(); // 数据成员指针,指向fuck成员
  14. auto p2 = shit(); // 数据成员指针,指向shit成员
  15. int T::* p1 = fuck(); // 同上
  16. int T::* p2 = shit(); // 同上
  17. decltype(fuck()) p1 = fuck(); // 同上
  18. decltype(shit()) p2 = shit(); // 同上
  19. T t;
  20. std::cout << t.*p1 << std::endl; // 打印fuck成员
  21. std::cout << t.*p2 << std::endl; // 打印shit成员
  22. }

成员函数指针

指向成员函数的类成员指针。不同于函数指针,这不是可调用对象,必须要先绑定到类对象。

  1. class T {
  2. public:
  3. void fuck() const{
  4. std::cout << "Base::fuck" << std::endl;
  5. }
  6. }
  7. int main(){
  8. // ****************************************************
  9. // 声明方式(成员函数指针)
  10. // ****************************************************
  11. // 声明方式1
  12. void (T::*pFunc)() const; // pFunc是类成员函数指针,指向的函数签名形式void()const
  13. pFunc = &T::fuck;
  14. pFunc = T::fuck; // 错误,在成员函数和指针之间不存在自动转换规则
  15. // 声明方式2
  16. auto pFunc = &T::fuck; // 同上
  17. // 声明方式3
  18. decltype(&T::fuck) pFunc = &T::fuck;
  19. // 声明方式4,using或者typedef
  20. using FuncPtr = void (T::*) const;
  21. FuncPtr pFunc = &T::fuck;
  22. // ****************************************************
  23. // 使用方式(成员函数指针)
  24. // ****************************************************
  25. T t, *pt = &t;
  26. (t.*pFunc)(); // 执行func函数,别漏了前面的括号。
  27. (t->*pFunc)(); // 执行func函数
  28. }

实践例子

移动光标。

  1. int main(){
  2. Screen myScreen;
  3. myScreen.move(Screen::HOME); // 调用myScreen.home
  4. myScreen.move(Screen::DOWN); // 调用myScreen.down
  5. }
  6. class Screen {
  7. public:
  8. using Action = Screen& (Screen::*)();
  9. enum Directions { HOME, FORWARD, BACK, UP, DOWN };
  10. public:
  11. //其他接口和 实现成员 与之前一致
  12. Screen& home(); //光标移动函数
  13. Screen& forward();
  14. Screen& back();
  15. Screen& up();
  16. Screen& down();
  17. Screen& move(Directions cm){
  18. //运行this对象中索引值为cm的元素
  19. return (this->*Menu[cm])(); //Menu[cm]指向一个成员函数
  20. }
  21. private:
  22. static Action Menu[] = { //函数表
  23. &Screen::home,
  24. &Screen::forward,
  25. &Screen::back,
  26. &Screen::up,
  27. &Screen::down,
  28. }
  29. };

变成可调用对象

成员函数指针本身不是可调用对象,必须利用.或者->运算符先绑定到对象上。以下代码是错误示例:

  1. auto fp = &string::empty; // fp指向string的empty函数
  2. // 错误,fp不是可调用对象,它是一个类成员函数指针。
  3. find_if(svec.begin(), svec.end(), fp);

思路

1、可调用对象,就是重载调用运算符
2、类对象初始化的时候传入类成员函数指针
3、调用运算符形参传入类对象,接着成员函数指针绑定到这个类对象上,然后执行。

  1. class T{
  2. ......
  3. bool operator()(const string& str){...} // 传入成员函数指针绑定的类对象。
  4. bool operator()(const string* str){...} // 指针版本
  5. ......
  6. };
  7. string str = "asdfasdf";
  8. T t(&string::empty); // 初始化的时候传入成员函数指针
  9. t(str); // 执行的时候,传入类对象。内部则绑定成员函数指针并执行。
  10. t(&str); // 指针版本

完整代码如下:

  1. class T
  2. {
  3. public:
  4. using Action = bool ( string::* )( ) const; // 指向类string的这样的成员函数bool()
  5. public:
  6. T( Action action ) : _action( action ), _pStr(nullptr) {}
  7. bool operator()( const string &str ) { // 引用版本
  8. return ( str.*_action )();
  9. }
  10. bool operator()(const string* pStr){ // 指针版本
  11. return ((*pStr).*_action)();
  12. }
  13. private:
  14. string* _pStr;
  15. Action _action;
  16. };
  17. int main()
  18. {
  19. std::vector<string> svec = { "abc", "bca", "A" };
  20. T t( &string::empty );
  21. auto findIt = svec.end();
  22. if( ( findIt = find_if( svec.begin(), svec.end(), t ) ) != svec.end() ) {
  23. std::cout << "find it." << std::endl;
  24. }
  25. return 0;
  26. }

方法一:std::function

std::function已将上述原理过程进行封装,我们直接拿来用即可。

  1. // 初始化的时候,定好成员函数指针
  2. // <>尖括号里的特例化了重载调用运算符的签名bool(const string&)
  3. function<bool(const string&)> fcn = &string::empty;
  4. // 在find_if内部以这种方式调用函数对象fcn:bool ret = fcn(*it); *it返回一个元素的引用。
  5. find_if(svec.begin(), svec.end(), fcn);

方法二:mem_fn

上面的std::function还有点麻烦,就是我们需要显式声明成员函数的签名,我们完全可以让编译器完成这个功能,标准库函数mem_fn即可完成这个需求。
mem_fn直接返回一个可调用对象。

  1. vector<string> svec = {......};
  2. // shit直接就是一个可调用对象
  3. auto shit = mem_fn(&string::empty);
  4. // 自动推断出:是接受一个指向string的引用,然后使用.*调用empty
  5. find_if(svec.begin(), svec.end(), shit);

方法三:std::bind

  1. vector<string> svec = {......};
  2. auto f = bind(&string::empty, _1);
  3. find_if(svec.begin(), svec.end(), f);
  4. f(*svec.begin()); // 引用版本
  5. f(&svec[0]); // 指针版本