在 C++ 中,所有可以像函数一样被调用的实体,统称为可调用对象(Callable Objects)。这些对象不仅仅局限于普通函数,还包括类的静态成员函数、仿函数(Functors)、lambda 表达式、类的非静态成员函数以及可转换为函数指针的类对象。本文将详细介绍这些不同的可调用对象,并探讨它们的使用场景及特点。嗯,语气比较严肃。!_!


一、普通函数

普通函数是最常见的可调用对象。在 C++ 中,普通函数可以通过函数指针和函数引用来存储和调用。

1.1 普通函数的定义与调用

普通函数的声明非常简单,我们可以为普通函数类型定义别名,并声明函数指针或函数引用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
using namespace std;

using Fun = void (int, const string&);  // 定义普通函数类型的别名

// 函数声明
Fun show;

int main()
{
    show(1, "我是普通函数调用。");

    void(*fp1)(int, const string&) = show;  // 函数指针
    void(&fr1)(int, const string&) = show;  // 函数引用
    fp1(2, "使用函数指针调用。");
    fr1(3, "使用函数引用调用。");

    Fun* fp2 = show;  // 使用别名创建函数指针
    Fun& fr2 = show;  // 使用别名创建函数引用
    fp2(4, "使用别名的函数指针。");
    fr2(5, "使用别名的函数引用。");
}

// 函数定义
void show(int bh, const string& message) {
    cout << "编号" << bh << ":" << message << endl;
}

1.2 注意事项

  • 普通函数可以通过函数指针和函数引用来调用。
  • 函数的类型可以通过 using 来定义别名,以简化代码。
  • C++ 中不能用函数类型定义函数的实体,函数类型只能用于声明。

二、类的静态成员函数

类的静态成员函数与普通函数本质上相同。它们与类的实例无关,因此可以直接通过类名或函数指针进行调用。

2.1 静态成员函数的定义与调用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;

struct MyClass {
    static void show(int bh, const string& message) {
        cout << "编号" << bh << ":" << message << endl;
    }
};

int main() {
    MyClass::show(1, "直接调用静态成员函数。");

    void(*fp1)(int, const string&) = MyClass::show;  // 函数指针指向静态成员函数
    fp1(2, "使用函数指针调用静态成员函数。");

    using Fun = void (int, const string&);
    Fun* fp2 = MyClass::show;
    fp2(3, "使用别名的函数指针调用静态成员函数。");
}

2.2 特点

  • 静态成员函数不依赖于对象实例,可以像普通函数一样调用。
  • 与普通函数类似,静态成员函数也可以使用函数指针和函数引用来存储和调用。

三、仿函数(Functor)

仿函数本质上是一个重载了 operator() 运算符的类。通过这种运算符重载,类的对象可以像函数一样被调用。

3.1 仿函数的定义与调用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <iostream>
using namespace std;

struct Functor {
    void operator()(int bh, const string& message) {
        cout << "编号" << bh << ":" << message << endl;
    }
};

int main() {
    Functor fun;
    fun(1, "调用仿函数。");

    Functor()(2, "调用匿名对象的仿函数。");
}

3.2 特点

  • 仿函数是通过 operator() 运算符来模拟函数调用的类对象。
  • 仿函数常用于 STL 算法中,它们可以存储状态,比普通函数更灵活。

四、Lambda表达式

Lambda 表达式本质上是一个匿名的仿函数。它是 C++11 引入的一个特性,用于简洁地定义内联函数。

4.1 Lambda表达式的定义与调用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <iostream>
using namespace std;

int main() {
    auto lambda = [](int bh, const string& message) {
        cout << "编号" << bh << ":" << message << endl;
    };

    lambda(1, "调用Lambda表达式。");

    auto& lambda_ref = lambda;  // 引用Lambda表达式
    lambda_ref(2, "引用Lambda表达式调用。");
}

4.2 特点

  • Lambda 表达式是一种简洁的匿名函数对象,通常用于一次性操作。
  • Lambda 表达式的类型是匿名的,因此只能通过 auto 或模板进行类型推导。

五、类的非静态成员函数

类的非静态成员函数不同于普通函数和静态成员函数,它只能通过类的实例进行调用。其指针类型特殊,不能通过引用调用。

5.1 非静态成员函数的定义与调用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <iostream>
using namespace std;

struct MyClass {
    void show(int bh, const string& message) {
        cout << "编号" << bh << ":" << message << endl;
    }
};

int main() {
    MyClass obj;
    obj.show(1, "直接调用非静态成员函数。");

    void (MyClass::* fp)(int, const string&) = &MyClass::show;
    (obj.*fp)(2, "通过成员函数指针调用。");
}

对于普通函数,你可以直接写函数名来获取它的地址,但对于成员函数,由于它依赖于类和对象,必须通过 & 明确表示你是在获取地址,而不是在引用或声明函数。

5.2 特点

  • 非静态成员函数必须通过对象调用,不能像普通函数一样通过函数指针直接调用。
  • 只能使用成员函数指针来调用非静态成员函数。

六、可转换为函数指针的类对象

通过重载 operator() 或重载类型转换运算符,类对象可以被转换为函数指针,使得类的对象也能像函数一样调用。这个,我认为仅作了解吧,我不是很理解。

6.1 可转换为函数指针的类对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;

void show(int bh, const string& message) {
    cout << "编号" << bh << ":" << message << endl;
}

struct CallableClass {
    using Fun = void(*)(int, const string&);
    operator Fun() {
        return show;
    }
};

int main() {
    CallableClass callable;
    callable(1, "调用可转换为函数指针的类对象。");
}

6.2 特点

  • 这种方式允许将类对象隐式转换为函数指针,调用方式类似函数对象。
  • 在实际开发中,这种技巧较为少见,但可以用于某些特殊需求场景。

总结

C++中的可调用对象种类丰富且灵活,包括普通函数、静态成员函数、仿函数、lambda 表达式、非静态成员函数以及可转换为函数指针的类对象。每种可调用对象都有其特定的使用场景与优点,在编写泛型代码、设计类接口以及高效处理函数调用时,这些可调用对象能够极大地提升代码的灵活性与可扩展性。