前言
在C++开发与面试中,成员函数指针、std::function包装器是高频重难点。绝大多数初学者都会疑惑:
为什么普通函数可以直接取地址,而类成员函数必须加 &、必须带类域?
成员函数指针为什么不能直接调用,必须用
.*/->*?std::function 包装静态/非静态成员函数的区别是什么?
为什么包装非静态成员函数需要多传一个类对象参数?
std::bind 如何简化成员函数调用、工程中如何做函数分发表?
本文整合所有底层原理、语法规则、全套可编译代码、流程图、易错点、工程拓展、面试考点,一次性彻底吃透整套知识点。
一、整体知识体系总流程图
贯穿全文的核心逻辑,帮你建立全局认知:
二、前置核心概念:三类函数本质区别
1. 全局自由函数
不属于任何类、无所属对象,无隐式参数,语法最简单,支持函数名隐式转函数指针。
2. 类静态成员函数 static
归属类,不归属对象,无隐式this指针,内存和行为近似“作用域被限制在类内的全局函数”,不遵循成员函数指针语法。
3. 类非静态成员函数(普通成员函数)
归属类对象实例,编译器自动补第一个隐式参数类* const this,必须依托对象才能调用,拥有独立、特殊的语法规则。
三、底层原生语法:成员函数指针完全解析
这是 std::function 封装的底层基础,所有上层封装都基于此原理。
1. 完整可编译代码
#include <iostream> using namespace std; class A { public: // 普通非静态成员函数 void func() { cout << "A::func()" << endl; } // const 修饰成员函数 void func_const() const { cout << "A::func_const() const" << endl; } // 静态成员函数 static void staticFunc() { cout << "A::staticFunc() 静态成员函数" << endl; } }; int main() { // 1. 非静态成员函数指针定义与取地址 typedef void(A::*PF)(); PF pf = &A::func; // ✅唯一合法写法 // PF pf = A::func; // ❌编译报错:非静态成员无隐式转换 A obj; (obj.*pf)(); // ✅对象调用 .* A* pObj = new A; (pObj->*pf)(); // ✅对象指针调用 ->* delete pObj; // 2. const成员函数指针:签名必须严格匹配 typedef void(A::*PFConst)() const; PFConst pfConst = &A::func_const; (obj.*pfConst)(); // 3. 静态成员函数:普通函数指针即可接收 void (*staticP)() = A::staticFunc; void (*staticP2)() = &A::staticFunc; staticP(); staticP2(); return 0; }2. 核心语法详解
(1)成员函数指针声明语法
返回值 (类名::*指针名)(参数列表)示例:void (A::*pf)()
和普通函数指针完全不是同一类型:
void (*p)():全局函数指针void (A::*pf)():A类专属成员函数指针
(2)取地址铁律(面试必考)
普通函数:支持隐式转换,
fun/&fun都可以非静态成员函数:禁止隐式转换,必须写
&类名::函数名静态成员函数:同普通函数,可省略
这就是原文注释:成员函数要指定类域并且前面加&才能获取地址的本质来源。
(3)专属调用运算符与括号陷阱
对象实例调用:
(obj.*pf)()对象指针调用:
(pObj->*pf)()
⚠️括号绝对不能省:./->优先级高于函数调用(),漏写括号会导致编译错误。
(4)底层核心原理:隐式this参数
你写的成员函数:
void A::func()编译器底层翻译为:
void func(A* const this)所有非静态成员函数,本质上第一个参数永远是this指针,这也是为什么成员函数必须依赖对象调用的根本原因。
(5)const成员函数严格匹配规则
const属于函数签名的一部分,定义成员函数指针时必须带const,否则类型不匹配。
(6)高危避坑:禁止用void*保存成员函数指针
普通指针固定8字节,但成员函数指针在多继承、虚继承下大小不固定,会存储偏移信息。强行转void*会引发未定义行为。
四、现代C++高阶封装:std::function全方位解析
std::function是C++11通用可调用对象包装器,统一封装:全局函数、Lambda、仿函数、成员函数指针,抹平原生成员指针晦涩语法。
1. 完整可编译工程代码
#include <iostream> #include <functional> #include <map> #include <utility> using namespace std; class Plus { public: // 静态成员函数:无this指针 static int plusi(int a, int b) { return a + b; } // 非静态成员函数:底层自带 Plus* this double plusd(double a, double b) { return a + b; } }; int main() { // 1. 包装静态成员函数:无需对象,和普通函数一致 function<int(int, int)> f4 = &Plus::plusi; cout << "f4(1, 1) = " << f4(1, 1) << "\n\n"; // 2. 非静态成员:传对象指针 function<double(Plus*, double, double)> f5 = &Plus::plusd; Plus pl; cout << "f5(&pl, 1.111, 1.1) = " << f5(&pl, 1.111, 1.1) << "\n\n"; // 3. 非静态成员:值传递对象(发生拷贝) function<double(Plus, double, double)> f6 = &Plus::plusd; cout << "f6(pl, 1.1, 1.1) = " << f6(pl, 1.1, 1.1) << "\n"; cout << "f6(Plus(), 1.1, 1.1) = " << f6(Plus(), 1.1, 1.1) << "\n\n"; // 4. 非静态成员:右值引用,接收临时对象/move对象 function<double(Plus&&, double, double)> f7 = &Plus::plusd; cout << "f7(move(pl), 1.1, 1.1) = " << f7(move(pl), 1.1, 1.1) << "\n"; cout << "f7(Plus(), 1.1, 1.1) = " << f7(Plus(), 1.1, 1.1) << "\n\n"; // 工程实战:map + function 实现命令分发表 map<string, function<int(int, int)>> opFuncMap = { {"+", [](int x, int y) { return x + y; }} }; cout << "运算结果:" << opFuncMap["+"](3, 5) << endl; return 0; }2. 核心包装规则
(1)静态成员函数
无this指针,包装方式和普通函数完全一致,无需传递对象。
(2)非静态成员函数(核心重点)
因为底层自带隐式this参数,std::function的第一个模板参数,专门用来传递类对象,支持四种传参形式:
类*:传递对象指针,效率最高,无拷贝
类:值传递,会拷贝构造新对象
类&:左值引用,绑定已有对象
类&&:右值引用,适配临时对象、move转移后的对象
五、进阶拓展:std::bind 预绑定对象
直接用std::function包装成员函数,每次调用都要手动传对象。std::bind可以提前绑定对象,彻底抹平this参数,简化调用接口。
完整拓展代码
#include <iostream> #include <functional> using namespace std; class Plus { public: double plusd(double a, double b) { return a + b; } }; int main() { Plus pl; // 绑定成员函数 + 对象,占位符保留业务参数 function<double(double, double)> f_bind = bind(&Plus::plusd, &pl, placeholders::_1, placeholders::_2); // 无需再传对象! cout << "bind调用结果:" << f_bind(2.2, 3.3) << endl; // 绑定临时对象 function<double(double, double)> f_temp = bind(&Plus::plusd, Plus(), placeholders::_1, placeholders::_2); cout << "临时对象绑定结果:" << f_temp(10, 20) << endl; return 0; }六、全套核心知识点总结(可直接背诵)
非静态成员函数取地址强制规则:必须
&类名::函数名,&不可省略、必须带类域,无隐式转换。静态成员函数:无this指针,语法等同于全局函数,不遵循成员指针规则。
成员函数指针语法:
R(类::*pf)(参数),与普通函数指针类型不互通。专属调用符:
.*、->*,外层括号不可省略。底层本质:所有非静态成员函数自带隐式
this指针,必须依托对象调用。std::function包装规则:非静态成员函数第一个参数固定用于传递对象(指针/值/引用/右值引用)。
const严格匹配:const成员函数的指针必须带const修饰,否则类型不匹配。
std::bind作用:预绑定对象,简化上层调用,消除手动传this的冗余。
工程用法:
std::function + map实现通用回调、命令分发表。
1. 为什么成员函数取地址必须加&,普通函数不用?
C++标准为了严格区分成员函数名与成员函数指针,禁止非静态成员函数隐式转换;而普通全局函数为了语法简洁,允许隐式转换。
2. 成员函数指针可以存入容器吗?
原生成员指针可以存入容器,但类型绑定固定类、通用性极差。工程中一律推荐使用std::function。
3. 虚函数的成员函数指针存的是什么?
不存储具体函数地址,存储虚表偏移量,运行时根据对象真实类型动态绑定多态函数。