函数指针是存储函数入口地址的变量,定义需严格匹配返回类型、参数列表及限定符(如const/noexcept/调用约定);推荐using定义回调类型,支持模板别名且可读性高。

怎么写一个指向函数的指针变量
函数指针本质是变量,它存储的是函数的入口地址,不是函数本身。定义时必须和目标函数的返回类型、参数个数与类型完全一致。
比如有函数 int add(int a, int b),它的函数指针类型是 int (*)(int, int);声明变量就得写成:
int (*func_ptr)(int, int) = add;
注意括号不能省:int *func_ptr(int, int) 是函数声明(返回 int*),不是函数指针。
常见错误:
立即学习“C++免费学习笔记(深入)”;
- 漏掉最外层括号,导致被解析为函数声明
- 参数类型写错,比如把
const std::string&写成std::string,编译失败 - 用
auto推导时忘记加&或*,比如auto p = &add正确,auto p = add会退化为函数类型而非指针
typedef 和 using 哪个更适合定义回调类型
using 更推荐——语法更直观、支持模板别名、可读性高;typedef 在复杂声明里容易绕晕人,尤其嵌套函数指针时。
对比定义同一类型:
typedef int (*callback_t)(double, const char*);
using callback_t = int (*)(double, const char*);
两者等价,但 using 的等号形式更贴近“变量赋值”直觉,且能轻松扩展为模板别名:
template
using handler_t = void (*)(T, bool);
handler_th1 = [](int x, bool ok) { /* ... */ }; // OK
typedef 无法直接做这种泛型绑定。
定义带 const / noexcept / 引用限定符的函数指针类型
如果目标函数带 noexcept、const 成员限定或右值引用限定,函数指针类型也必须严格匹配,否则编译报错。
例如类成员函数:
struct Foo {
void method() const noexcept { }
};
void (Foo::*ptr)() const noexcept = &Foo::method;普通函数若声明为 void f() noexcept,其指针类型就是 void (*)() noexcept;漏掉 noexcept 就不兼容。
关键点:
- 成员函数指针必须用
ClassName::*语法,且要带上const/volatile/&/&&限定符 - 普通函数的
noexcept是类型系统一部分,不是修饰符,必须写在函数指针声明里 - C++17 起,函数类型中的
noexcept影响重载决议和模板推导
回调函数类型定义中容易忽略的 ABI 兼容问题
Windows 上 __stdcall、__cdecl 等调用约定是类型的一部分。用错会导致栈损坏或崩溃,但编译器不一定报错。
例如 Win32 API 的 WNDPROC 实际是:
using WNDPROC = LRESULT (CALLBACK*)(HWND, UINT, WPARAM, LPARAM);
其中 CALLBACK 展开为 __stdcall。如果你用 using my_proc = LRESULT (*)(HWND, UINT, WPARAM, LPARAM)(默认 __cdecl)去赋值,运行时可能 crash。
所以:
- 跨平台库尽量避免显式调用约定,依赖默认
- 对接系统 API 时,务必查文档确认调用约定,并在类型定义中显式写出(如
__stdcall) - Clang/GCC 下可用
__attribute__((stdcall))模拟,MSVC 下用__stdcall
函数指针的“类型安全”比看起来更脆弱——差一个 const、一个 noexcept、一个调用约定,都可能导致未定义行为,而编译器有时只给弱警告。









