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. 通过对象指针调用 (使用 ->* 运算符)
(对象指针名->*指针变量名)(参数列表);

⚠️ 类成员函数指针的两大硬性法则

  1. 必须显式使用取地址符 &

    普通函数名可以隐式转换为函数指针,但类成员函数不行。赋值时必须写完整的 &类名::成员函数名

    注:虽然部分旧编译器或非标准扩展可能允许省略 &(如GCC),但在现代 C++ 标准中,省略 & 会导致编译报错。

  2. 必须绑定对象调用,且外层括号是强制性的

    由于成员函数需要 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;
}
posted @ 2026-07-17 12:25  学习笔记草稿存放账号  阅读(1)  评论(0)    收藏  举报