在 C++ 中,可调用对象是可以像函数一样使用的对象。可调用对象包括以下几种类型:

【像函数一样的调用对象】

  • 是⼀个函数指针。

  • 是⼀个具有operator()成员函数的类对象(传说中的仿函数),lambda表达式。

  • 是⼀个可被转换为函数指针的类对象。

  • 是⼀个类成员(函数)指针。

  • bind表达式或其它函数对象。

普通函数

这里来个最简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<bits/stdc++.h>

using namespace std;
int add(int a,int b){
return a+b;
}
signed main(){
int num1,num2;
cin>>num1>>num2;
int sum=add(num1,num2);
cout<<sum<<endl;
return 0;
}

类成员函数

类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量⼀样。类成员函数是类的⼀个成员,它可以操作类的任意对象,可以访问对象中的所有成员。

来看下面这个例子:

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
#include<bits/stdc++.h>

using namespace std;

class Person
{

public:
Person(string name,int age):name_(name),age_(age){

}
int getAge(){
return age_;
}
string getName(){
return name_;
}
private:
string name_;
int age_;
};
signed main(){
Person p("penge666",18);
cout<<p.getAge()<<endl;
return 0;
}

类静态函数

类中的静态成员函数作⽤在整个类的内部,对应类的所有实例是共享静态成员函数的,在调⽤静态成员函数的时候跟调⽤⾮静态成员函数是有区别的。另外,静态成员函数只能访问对应类内部的静态数据成员,否则会出现编译错误。因为没有this指针!

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
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <vector>
#include <thread>
#include <queue>
#include <functional>
#include <condition_variable>
#include <mutex>
#include <future>
using namespace std;

class Person
{

public:
Person(string name) : name_(name)
{
}
static void printAge()
{
// cout << "name:" << name_ << ",age:" << age_ << endl; // error
cout << "age:" << age_ << endl;
return;
}
string getName()
{
return name_;
}

private:
string name_;
static int age_;
};
int Person::age_ = 18;
signed main()
{
Person p("penge666");
p.printAge();
return 0;
}

Note

静态成员函数

  • 静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。

  • 普通成员函数有 this 指针,可以访问类中的任意成员;⽽静态成员函数没有 this 指针。

静态成员变量:类内声明,类外定义【无需写上staitic关键字】。

仿函数

仿函数其实就是重载了operator()运算符的类对象。可以视为⼀个⼀般的函数,只不过这个函数功能是在⼀个类中的运算符operator()中实现,是⼀个函数对象,它将函数作为参数传递的⽅式来使⽤。(⾏为类似函数,故称仿函数)。实际上就是创建⼀个类,该类重载了operator()运算符,使得类的实例可以像函数⼀样被调⽤。这允许你在函数对象内部保存状态,并在调⽤时执⾏操作。

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
28
29
30
#include <iostream>
#include <vector>
#include <thread>
#include <queue>
#include <functional>
#include <condition_variable>
#include <mutex>
#include <future>
using namespace std;

class Person
{

public:
void operator()()
{
cout << __FUNCTION__ << '\n';
}

private:
};

signed main()
{
Person p;
p.operator()();
p();
Person()();
return 0;
}

在类中重载了⼀个括号运算符,有⼏种⽅法可以调⽤这个函数呢?

  1. 定义⼀个结构体对象,然后显式访问这个函数:operator(),需要注意的是,后⾯得再加上⼀个括号。

  2. 通过结构体对象直接访问函数。

  3. 通过结构体匿名对象,访问并调⽤函数

可以方便计算!

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
28
29
30
31
32
#include <iostream>
#include <vector>
#include <thread>
#include <queue>
#include <functional>
#include <condition_variable>
#include <mutex>
#include <future>
using namespace std;

template <class Fun>
int count_if(const std::vector<int>::iterator &a, const std::vector<int>::iterator &b, Fun fun)
{
int sum = 0;
for (auto it = a; it != b; it++)
{
if (fun(*it))
sum++;
}
return sum;
}
bool equal_fun(int num)
{
return num == 3;
}
signed main()
{
vector<int> a{1, 2, 3, 3, 3, 3, 3, 4, 4};
int ans = count_if(a.begin(), a.end(), equal_fun);
cout << ans << endl;
return 0;
}

写成仿函数的形式

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
28
29
30
31
32
33
34
35
36
#include <iostream>
#include <vector>
#include <thread>
#include <queue>
#include <functional>
#include <condition_variable>
#include <mutex>
#include <future>
using namespace std;
template <class Fun>
int count_if(const std::vector<int>::iterator &a, const std::vector<int>::iterator &b, Fun fun)
{
int sum = 0;
for (auto it = a; it != b; it++)
{
if (fun(*it))
sum++;
}
return sum;
}
struct Equal_fun
{
int val_;
Equal_fun(const int &val) : val_(val) {}
bool operator()(const int a)
{
return a == val_;
}
};
signed main()
{
vector<int> a{1, 2, 3, 3, 3, 3, 3, 4, 4};
int ans = count_if(a.begin(), a.end(), Equal_fun(3));
cout << ans << endl;
return 0;
}

1.优点

  • 仿函数⽐函数指针的执⾏速度快,函数指针是通过地址调⽤,⽽仿函数是对运算符operator进⾏⾃定义来提⾼调⽤的效率。

    • 内联优化。同时编译器会对对象进行优化
    • 函数指针调用的间接性,因为函数地址是在运行时确定的。每次调用函数指针时,CPU 必须通过指针跳转到实际的函数地址,这增加了额外的开销。
  • 仿函数⽐⼀般函数灵活,可以同时拥有两个不同的状态实体,⼀般函数不具备此种功能。

  • 仿函数可以作为模板参数使⽤,因为每个仿函数都拥有⾃⼰的类型。

2.缺点

  • 需要单独实现⼀个类。

  • 定义形式⽐较复杂。

函数指针

double proto(int);的函数指针是double (*pf)(int);

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<bits/stdc++.h>

using namespace std;

void print(int x) {
std::cout << "Value: " << x << std::endl;
}

int main() {
void (*funcPtr)(int) = print;
funcPtr(5); // 间接调用,不能内联
return 0;
}

Lambda函数

1
2
3
4
5
6
7
8
9
10
#include <iostream>

int main() {
auto print = [](int a) {
std::cout << "Value: " << a << std::endl;
};

print(5); // 调用 Lambda 表达式
return 0;
}

std::function与std::bind

std::function是⼀个可调⽤对象包装器,是⼀个类模板,可以容纳除了类成员函数指针之外的所有可调⽤对象,它可以⽤统⼀的方式处理函数、函数对象、函数指针,并允许保存和延迟它们的执行。

std::function

std::function的实例可以存储、复制和调用任何可调⽤对象,存储的可调⽤对象称为std::function的⽬标,若std::function不含目标,则称它为空,调⽤空的std::function的⽬标会抛出std::bad_function_call异常。

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>
#include <functional>

void print(int a) {
std::cout << "Function: " << a << std::endl;
}
class Printer {
public:
void operator()(int a) const {
std::cout << "Functor: " << a << std::endl;
}
};
int main() {
std::function<void(int)> func;
// 使用普通函数
func = print;
func(5);
// 使用 Lambda 表达式
func = [](int a) {
std::cout << "Lambda: " << a << std::endl;
};
func(10);
// 使用函数对象(仿函数)
func = Printer();
func(15);
return 0;
}

std::bind

std::bind 是一个函数适配器,用于将函数与其参数绑定在一起,从而创建一个新的可调用对象。std::bind 允许部分应用函数参数(即“柯里化”),从而创建新的函数。

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

void add(int a, int b) {
std::cout << "Sum: " << a + b << std::endl;
}

int main() {
// 使用 std::bind 绑定第一个参数
auto add_five = std::bind(add, 5, std::placeholders::_1);
add_five(10); // 等同于 add(5, 10)

// 使用 std::bind 绑定两个参数
auto add_ten = std::bind(add, 5, 5);
add_ten(); // 等同于 add(5, 5)

return 0;
}

示例:std::bindstd::function 结合使用

std::bind 生成的可调用对象可以存储在 std::function 中,从而增强了灵活性。

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
#include <iostream>
#include <vector>
#include <thread>
#include <queue>
#include <functional>
#include <condition_variable>
#include <mutex>
#include <future>
using namespace std;

void sum(int a, int b)
{
cout << "sum:" << a + b << '\n';
}
signed main()
{
// way1:
function<void()> func;
func = bind(sum, 1, 2);
func();
// way2:
function<void(int)> func1;
func1 = bind(sum, 1, std::placeholders::_1);
func1(2);
return 0;
}

使用 std::functionstd::bind 来实现一个通用的事件系统:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
#include <vector>
#include <thread>
#include <queue>
#include <functional>
#include <condition_variable>
#include <mutex>
#include <future>
using namespace std;

class EventSystem
{
public:
void registerEvent(const function<void(int)> &func)
{
listeners.push_back(func);
}
void triggerEvent(int val)
{
for (const auto &listener : listeners)
{
listener(val);
}
}

private:
vector<function<void(int)>> listeners;
};
void printValue(int a)
{
std::cout << "Value: " << a << std::endl;
}

class Handler
{
public:
void handleEvent(int a)
{
std::cout << "Handled Value: " << a << std::endl;
}
};
signed main()
{
EventSystem eventSystem;
eventSystem.registerEvent(printValue);
eventSystem.registerEvent([](int a)
{ std::cout << "Lambda Handled Value: " << a << std::endl; });
// 注册成员函数
Handler handler;
eventSystem.registerEvent(std::bind(&Handler::handleEvent, &handler, std::placeholders::_1));

// 触发事件
eventSystem.triggerEvent(42);
return 0;
}