C++ 成员函数的指针

指向成员函数的指针(Pointer to Member Function)。


1. 基本语法示例

cpp 复制代码
class Data1 {
public:
    int a;
    int getA() const { return a; }
    void setA(int val) { a = val; }
};

// 声明并初始化一个指向成员函数的指针
int (Data1::* fp)() const = &Data1::getA;
void (Data1::* fp2)(int) = &Data1::setA;

2. 类型声明的语法拆解

int (Data1::* fp)() const 为例:

  • int:成员函数的返回值类型。
  • (Data1::* fp)Data1::* 表示"指向 Data1 的成员",fp 是指针变量名。括号是必需的,因为 () 的优先级高于 *
  • ():函数参数列表(这里是空参数)。
  • const:成员函数的常量限定符(如果有的话必须完全匹配)。

对比普通函数指针:

  • 普通函数指针:int (*p)(double) → 指向一个参数为 double、返回 int 的函数。
  • 成员函数指针:int (Data1::* p)(double) → 指向 Data1 的一个参数为 double、返回 int 的成员函数。

3. 初始化(赋值)

使用 &类名::成员函数名 获取成员函数的指针:

cpp 复制代码
int (Data1::* fp)() = &Data1::getA;
void (Data1::* fp2)(int) = &Data1::setA;

注意

  • 不能写成 &getA(缺少类作用域)。
  • 取成员函数的地址时,& 可以省略(C++ 允许隐式转换),但建议显式写出。
  • 静态成员函数的指针是普通函数指针,不是成员函数指针。

4. 如何调用(解引用)

必须结合具体对象或对象指针,使用 .*->* 运算符:

cpp 复制代码
Data1 obj;
obj.a = 100;

int (Data1::* fp)() = &Data1::getA;
int value = (obj.*fp)();   // 等价于 obj.getA()

Data1* p = &obj;
value = (p->*fp)();        // 等价于 p->getA()

关键点

  • 调用时必须加括号 (obj.*fp)(),因为 .* 优先级低于函数调用 ()
    若写成 obj.*fp() 会被解析为 obj.*(fp()),错误。
  • 可以保存返回值,传递参数同理。

5. 成员函数指针的类型必须精确匹配

包括:

  • 返回值类型
  • 参数类型(个数、顺序、const/引用等)
  • const / volatile / ref-qualifier (C++11 的 &&&)
cpp 复制代码
class Data1 {
public:
    int getA() const;      // const 成员
    int getA();            // 非 const 成员,重载
};

int (Data1::* fp1)() const = &Data1::getA;   // OK,指向 const 版本
int (Data1::* fp2)()       = &Data1::getA;   // OK,指向非 const 版本

// 错误:类型不匹配
// int (Data1::* fp3)() = &Data1::getA;  // 歧义?实际上编译器会选择非 const 版本,前提是只有一个匹配

如果存在重载,需要显式类型转换或通过上下文消除歧义,通常用 static_cast

cpp 复制代码
int (Data1::* fp)() const = static_cast<int (Data1::*)() const>(&Data1::getA);

6. 指向虚函数的成员指针

指向虚函数的指针同样遵循多态行为:

cpp 复制代码
class Base {
public:
    virtual void print() { cout << "Base\n"; }
};
class Derived : public Base {
public:
    void print() override { cout << "Derived\n"; }
};

void (Base::* pf)() = &Base::print;

Derived d;
Base* p = &d;
(p->*pf)();   // 输出 "Derived",因为虚函数通过指针调用会动态分派

7. 常用类型别名简化

cpp 复制代码
using GetAFunc = int (Data1::*)() const;
using SetAFunc = void (Data1::*)(int);

GetAFunc fp = &Data1::getA;
SetAFunc fp2 = &Data1::setA;

8. 与数据成员指针的异同

特性 数据成员指针 int Data1::* 成员函数指针 int (Data1::*)()
声明语法 int Data1::* p; int (Data1::* p)();
初始化 &Data1::a &Data1::func
需要对象/指针才能使用 obj.*pptr->*p (obj.*p)()(ptr->*p)()
调用/访问时加括号 不需要 ()(直接访问成员) 必须加 (),且括号位置特殊
支持重载 不涉及 需要类型转换消除歧义
支持虚函数 不涉及 支持,调用时动态分派
支持 const/volatile 成员本身的 const(指针声明无关) 必须与函数限定符完全匹配
底层实现 偏移量(字节) 通常为"thunk"或函数地址 + 调整量

9. 完整示例

cpp 复制代码
#include <iostream>
using namespace std;

class Data1 {
public:
    int a;
    Data1(int x) : a(x) {}
    int getA() const { return a; }
    void setA(int x) { a = x; }
    void print() const { cout << "a = " << a << endl; }
};

int main() {
    // 指向成员函数的指针
    int (Data1::* getter)() const = &Data1::getA;
    void (Data1::* setter)(int)   = &Data1::setA;
    void (Data1::* printer)() const = &Data1::print;

    Data1 obj(10);
    Data1* p = &obj;

    // 调用
    cout << (obj.*getter)() << endl;   // 10
    (p->*setter)(20);
    (obj.*printer)();                  // a = 20

    return 0;
}

10. 注意事项与最佳实践

  • 优先级和括号 :调用时 (obj.*ptr)() 不能省略外层的括号。
  • 类型别名 :推荐用 using 简化复杂的成员函数指针类型。
  • 指向静态成员函数:使用普通函数指针,而不是成员函数指针。
  • std::function 结合 :可以使用 std::function<int(const Data1&)> 包装成员函数,配合 std::bind 或 lambda 更现代。
cpp 复制代码
#include <functional>
auto getter = std::mem_fn(&Data1::getA);
int val = getter(obj);  // 更简洁

思考:

  • std::mem_fn 的原理与用法
  • 成员指针与模板元编程的结合
  • 成员指针在库设计中的实际案例(如信号槽、属性系统)
相关推荐
俺不要写代码2 小时前
线程启动、结束,创建线程多法、join,detach,线程的移动语义
服务器·开发语言·网络·c++
思麟呀2 小时前
应用层协议HTTP
linux·服务器·网络·c++·网络协议·http
小徐不徐说2 小时前
面试C++易错点总结
开发语言·c++·面试·职场和发展·程序设计·工作
xier_ran3 小时前
【C++】“内部”、“外部”、“派生类”、“友元“类
java·开发语言·c++
熬夜敲代码的猫4 小时前
C/C++:内存管理
c语言·c++·动态内存管理
故事和你914 小时前
洛谷-数据结构1-2-二叉树1
开发语言·数据结构·c++·算法·leetcode·动态规划·图论
大橘4 小时前
【qml-5.1】qml与c++交互(QML_ELEMENT/QML_SINGLETON)
开发语言·c++·qt·交互·qml
aq55356004 小时前
PHP vs C++ vs 易语言:编程语言终极对比
开发语言·c++·php
tankeven4 小时前
HJ181 相差不超过k的最多数
数据结构·c++·算法