C++语法补充 26.函数指针——编程Cat加加+Gemini
https://www.bilibili.com/video/BV12qy7Y2E5A
C++语法补充 26.函数指针——编程Cat加加+Gemini
在 C++ 中,指针用于表示内存中的地址。除了变量和数组拥有特定的物理内存地址外,函数同样是由一段编译后的可执行代码(指令)组成的,这些指令也存放在内存区域(通常是代码段/Text Segment)中。因此,函数也有其物理起始地址。
1. 函数地址与函数指针基础
1.1 函数名即地址
在 C++ 中,函数名本身在大多数上下文里会被隐式地转换为该函数的起始地址。这与数组名隐式转换为指向首元素的指针非常类似。
double multiply(double a, double b)
{
return a * b;
}
int main()
{
std::cout << std::hex << (unsigned long long)multiply << std::endl;
// 输出:7ff6a76b1450
}
1.2 定义普通函数指针
要定义一个能够指向特定函数的指针,该指针的签名(返回值类型及形参列表)必须与目标函数完全一致。其标准声明语法如下:
返回值类型 (*指针变量名)(参数类型列表);
以下为 multiply 函数对应的函数指针声明与赋值:
// 声明并初始化
double (*ptr)(double, double) = multiply;
// 调用方式(支持隐式直接调用和显示解引用调用)
double res1 = ptr(4.2, 1.5);
double res2 = (*ptr)(4.2, 1.5); // 效果完全相同
显示解引用调用时注意:
在解引用调用时,
(*ptr)的外括号绝不可省略。因为小括号()的优先级高于解引用符*,如果写成*ptr(4.2, 1.5),编译器会将其理解为:先调用函数ptr(...),然后再对该函数的返回值进行解引用,从而导致严重的语法错误。
2. 简化函数指针声明的现代方法
传统的 C/C++ 函数指针声明语法较为晦涩难懂,特别是在嵌套或者作为参数传递时。在实际的工程开发中,推荐使用以下三种方案来提升可读性:
方法 A:使用 typedef 定义类型别名
利用 typedef 可以为一种函数指针类型定义一个清爽的别名(类型别名使用大驼峰命名法以作区分):
// 定义一个名为 MyFuncTypePtr 的函数指针类型
typedef double (*MyFuncTypePtr)(double, double);
MyFuncTypePtr ptr1 = multiply;
double res1 = ptr1(4.2, 1.5);
方法 B:定义纯函数类型别名(无指针符号)
可以仅对函数本身签名定义别名,而在实际声明指针变量时再加上 *:
typedef double(MyFuncType)(double, double);
MyFuncType* ptr2 = multiply; // 显式指明为指针类型
方法 C:使用 auto 自动类型推导 (现代 C++ 风格)
自 C++11 起,可以使用 auto 关键字让编译器在编译期自动推导函数指针的类型。这是最干净利落的写法:
auto funcPtr = multiply; // 自动推导为 double(*)(double, double)
double res = funcPtr(4.2, 1.5);
📌
auto的局限性:
auto只能在声明时即进行显式初始化的场景下工作,且不能直接用于定义函数指针数组。在需要定义数组或者预先声明的场景下,仍需借助typedef或原生语法。
3. 典型应用场景:作为回调函数
函数指针最经典的应用是作为另一个函数的参数(即回调机制),这使得核心算法逻辑与业务条件能够完全解耦。例如,在标准库 std::sort 中传入自定义的比较逻辑:
#include <iostream>
#include <vector>
#include <algorithm>
bool compare(float a, float b) { return a < b; }
int main()
{
std::vector<float> numbers = {0.1f, 3.0f, -3.4f, 7.0f, 100.0f, -100.0f};
// 将比较函数 compare 的地址作为参数传入 std::sort
std::sort(numbers.begin(), numbers.end(), compare);
for(float number : numbers) { std::cout << number << " | "; }
std::cout << std::endl;
// 输出:-100 | -3.4 | 0.1 | 3 | 7 | 100 |
}
4. 类成员函数指针 (Member Function Pointer)
类成员函数(非静态)与普通函数有本质区别,因为非静态的类成员函数默认隐含了一个指向当前对象实例的 this 指针。因此,普通函数指针无法直接指向非静态成员函数,必须使用特定的类作用域限定语法来定义。
4.1 语法格式
定义与初始化:
返回值类型 (类名::*指针变量名)(参数列表) = &类名::成员函数名;
定义时,* 紧贴指针变量名,位于 类名:: 之后;赋值时,& 必须放在最前面。
调用方式:
根据调用主体的不同,分为对象与对象指针两种情况:
// 1. 通过对象或引用调用 (使用 .* 运算符)
(对象名.*指针变量名)(参数列表);
// 2. 通过对象指针调用 (使用 ->* 运算符)
(对象指针名->*指针变量名)(参数列表);
⚠️ 类成员函数指针的两大硬性法则
-
必须显式使用取地址符
&普通函数名可以隐式转换为函数指针,但类成员函数不行。赋值时必须写完整的
&类名::成员函数名。注:虽然部分旧编译器或非标准扩展可能允许省略
&(如GCC),但在现代 C++ 标准中,省略&会导致编译报错。 -
必须绑定对象调用,且外层括号是强制性的
由于成员函数需要
this指针,调用时必须搭配具体的对象实例。此外,因为运算符优先级的原因,.*和->*的优先级低于括号(),所以(对象.\*指针变量名)的外层括号绝对不能省略,否则编译器会将其解析为对函数调用结果的解引用。
4.2 示例代码
class DemoClass
{
public:
double add(double a, double b) { return a + b; }
double multiply(double a, double b) { return a * b; }
};
int main()
{
// 1. 声明类成员函数指针并初始化
double (DemoClass::*ptrMemberFunc)(double, double) = &DemoClass::add;
// 2. 通过对象实例进行调用
DemoClass obj;
double res1 = (obj.*ptrMemberFunc)(0.5, 2.1); // 使用 .* 运算符, 然后加上()
// 3. 通过对象指针进行调用
DemoClass* pObj = &obj;
ptrMemberFunc = &DemoClass::multiply;
double res2 = (pObj->*ptrMemberFunc)(0.5, 2.1); // 使用 ->* 运算符, 然后加上()
}
5. 成员函数指针的虚函数多态性
真实派上用场时,C++ 的成员函数指针天然支持虚函数的多态机制(Polymorphism)。如果成员函数指针指向的是一个虚函数(virtual),那么通过该指针和具体的对象实例调用该函数时,在运行期依然会触发动态绑定,从而调用派生类中重写(override)后的版本。
class BaseClass
{
public:
virtual void print() { std::cout << "Base Class\n"; }
};
class Derived : public BaseClass
{
public:
void print() override { std::cout << "Derived Class \n"; }
};
int main()
{
// 声明一个指向基类虚函数的指针
void (BaseClass::*pMemFunc)() = &BaseClass::print;
// 动态分配一个派生类对象,并用基类指针指向它
BaseClass* pObj = new Derived;
// 运行期动态绑定:输出 "Derived Class" 而不是 "Base Class"
(pObj->*pMemFunc)();
delete pObj;
}

浙公网安备 33010602011771号