1 函数声明

C++ 函数声明的完整范式可以表示为:

[存储类说明符] [内联说明符] [虚函数说明符] [constexpr] [静态断言]
返回类型 [调用约定] 函数名 (参数列表) [const] [volatile] [引用限定符] 
[异常说明] [尾置返回类型] [= default/delete/0];

1.1 详细解析

1.1.1 存储类说明符

static int func();      // 静态存储
extern int func();      // 外部链接
inline int func();      // 内联建议

1.1.2 CV 限定符和函数说明符

virtual int func();         // 虚函数
constexpr int func();       // 编译期计算
mutable int func();         // 可变(少用)

1.1.3 返回类型

int func();                 // 基本类型
auto func() -> int;         // 尾置返回类型
template<typename T> 
auto func() -> decltype(T{});  // 推导返回类型

1.1.4 调用约定(主要在 Windows)

int __cdecl func();         // C调用约定
int __stdcall func();       // 标准调用约定
int __fastcall func();      // 快速调用约定

1.1.5 参数列表

int func();                     // 无参数
int func(int a, double b);      // 普通参数
int func(int a = 10);           // 默认参数
int func(const int& a);         // 引用参数
int func(int&& a);              // 右值引用参数
template<typename... Args>
int func(Args... args);         // 可变参数模板
int func(int a, ...);           // C风格可变参数

1.1.6 CV 限定符(成员函数)

class MyClass {
    int func() const;           // 常量成员函数
    int func() volatile;        // volatile成员函数
    int func() const volatile;  // 同时具有两种限定
};

1.1.7 引用限定符(C++11)

class MyClass {
    int func() &;               // 只能被左值对象调用
    int func() &&;              // 只能被右值对象调用
    int func() const &;         // 常量左值
    int func() const &&;        // 常量右值
};

1.1.8 异常说明

int func() noexcept;                    // 不抛出异常
int func() noexcept(true);              // 不抛出异常
int func() noexcept(false);             // 可能抛出异常
int func() throw();                     // 旧式,不抛出异常(已废弃)
int func() throw(std::exception);       // 旧式,指定异常类型(已废弃)

1.1.9 特殊成员函数说明符

class MyClass {
    MyClass() = default;                // 默认实现
    MyClass(const MyClass&) = delete;   // 删除函数
    virtual ~MyClass() = 0;             // 纯虚函数
};

1.1.10 尾置返回类型(C++11)

auto func(int a, int b) -> int;         // 尾置返回类型
auto func() -> decltype(auto);          // 自动推导
 
template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u);  // 模板中的尾置返回类型

1.2 完整示例

class MyClass {
public:
    // 最复杂的函数声明示例
    static inline virtual constexpr 
    auto complexFunc(const int& a, double&& b, int c = 10) const noexcept 
    -> decltype(a + b + c);
    
    // 成员函数的完整形式
    mutable virtual auto memberFunc(int a) & noexcept(false) -> int = 0;
    
    // 普通函数的完整形式
    static constexpr inline auto staticFunc(int a, double b) noexcept -> double {
        return a * b;
    }
};
 
// 全局函数的完整形式
extern inline constexpr auto globalFunc(int a) noexcept -> int;
 
// 函数指针的完整形式
auto (*funcPtr)(int, double) noexcept -> int = nullptr;

1.3 注意事项

  1. 并非所有组合都有效:某些说明符互相冲突
  2. 语法顺序很重要:虽然某些说明符可以调换位置,但建议遵循标准顺序
  3. C++ 版本差异:一些特性需要特定 C++ 版本支持
  4. 编译器支持:某些特性可能需要特定编译器支持

这就是 C++ 函数声明的完整语法范式,实际使用中很少会用到所有这些特性。

2 函数指针声明

2.1 基本函数指针语法

函数指针的基本范式是

返回类型 (*指针名)(参数列表)

2.2 对比示例

// 普通函数声明
int func(int a, double b);
 
// 函数指针声明 - 不是简单的加*
int (*funcPtr)(int a, double b);  // 正确
int *funcPtr(int a, double b);    // 错误!这是返回int*的函数

2.3 为什么不能简单加 *

因为运算符优先级的问题:

int *funcPtr(int a);    // 解析为:int* funcPtr(int a) - 返回指针的函数
int (*funcPtr)(int a);  // 解析为:指向函数的指针

2.4 复杂函数指针示例

2.4.1 带 CV 限定符的成员函数指针

class MyClass {
public:
    int normalFunc(int a);
    int constFunc(int a) const;
    int volatileFunc(int a) volatile;
};
 
// 成员函数指针
int (MyClass::*memberPtr)(int) = &MyClass::normalFunc;
int (MyClass::*constMemberPtr)(int) const = &MyClass::constFunc;
int (MyClass::*volatileMemberPtr)(int) volatile = &MyClass::volatileFunc;

2.4.2 带异常说明的函数指针

// 函数指针包含异常说明
int (*noexceptPtr)(int) noexcept = nullptr;
int (*throwPtr)(int) = nullptr;  // 可能抛出异常的函数指针

2.4.3 复杂的函数指针类型

// 指向返回函数指针的函数的指针
int (*(*complexPtr)(int))(double);
 
// 解析:
// complexPtr 是一个指针
// 指向一个函数,该函数接受int参数
// 返回一个函数指针,该函数指针指向接受double参数、返回int的函数

2.4.4 数组中的函数指针

// 函数指针数组
int (*funcArray[10])(int, double);
 
// 指向函数指针数组的指针
int (*(*arrayPtr)[10])(int, double);

2.5 使用 typedef/using 简化

// 使用typedef简化复杂函数指针
typedef int (*FuncPtr)(int, double);
typedef int (MyClass::*MemberPtr)(int) const;
 
// C++11 using别名
using FuncPtr = int(*)(int, double);
using MemberPtr = int(MyClass::*)(int) const;
 
// 使用别名
FuncPtr ptr = someFunction;
MemberPtr memPtr = &MyClass::someMethod;

2.6 函数指针的完整语法范式

[返回类型] ([类名::] *[指针名]) ([参数列表]) [CV限定符] [异常说明]

2.6.1 完整示例

class MyClass {
public:
    static int staticFunc(int a) noexcept;
    int memberFunc(int a, double b) const;
    virtual int virtualFunc(int a) volatile;
};
 
// 各种函数指针声明
int (*staticPtr)(int) noexcept = &MyClass::staticFunc;
int (MyClass::*memberPtr)(int, double) const = &MyClass::memberFunc;
int (MyClass::*virtualPtr)(int) volatile = &MyClass::virtualFunc;
 
// 复杂的函数指针
auto (*complexPtr)(int) noexcept -> int = [](int x) noexcept -> int { return x; };

2.7 实际使用建议

由于函数指针语法复杂,实际开发中建议:

  1. 使用类型别名
using Callback = std::function<int(int, double)>;  // 更灵活
using FuncPtr = int(*)(int, double);               // 纯函数指针
  1. 使用 auto 推导
auto funcPtr = &someFunction;  // 让编译器推导类型
  1. 使用 std::function
std::function<int(int, double)> callback = someFunction;  // 更通用

所以函数指针绝不是简单地在函数名前加 *,而是需要用括号明确优先级,并且可能包含各种限定符。

3 成员函数指针作为成员变量 vs 返回成员函数指针的成员函数?

TL;DR看括号的位置 - 如果函数名后面直接跟括号,就是返回函数指针的函数;如果是变量名,就是函数指针成员变量

示例:

#include <iostream>
 
class Calculator {
 private:
  // 成员函数指针作为成员变量
  int (Calculator::*operation)(int, int);
  // 返回成员函数指针的成员函数
  int (Calculator::* getOperation(char op))(int, int);
 
 public:
  int add(int a, int b) { return a + b; }
  int subtract(int a, int b) { return a - b; }
 
  void setOperation(char op) {
    operation = getOperation(op);  // 调用返回函数指针的函数
  }
 
  int calculate(int a, int b) {
    if (operation) {
      return (this->*operation)(a, b);  // 调用成员函数指针
    }
    return 0;
  }
};
 
int (Calculator::* Calculator::getOperation(char op))(int, int) {
  switch (op) {
    case '+':
      return &Calculator::add;
    case '-':
      return &Calculator::subtract;
    default:
      return nullptr;
  }
}
 
int main() {
  Calculator calc;
  calc.setOperation('+');
  std::cout << "Addition: " << calc.calculate(5, 3) << std::endl;
  calc.setOperation('-');
  std::cout << "Subtraction: " << calc.calculate(5, 3) << std::endl;
  calc.setOperation('*');  // Invalid operation
  std::cout << "Invalid operation: " << calc.calculate(5, 3) << std::endl;
  return 0;
}