函数 Function与函数指针
[TOC]
1. 函数指针
1.1 含义
函数指针(Function Pointer)用于实现动态函数调用、回调函数、函数指针数组。
一个函数指针的形式如下:
1
int (*funcPtr)(int); // 指向一个接受一个int参数并返回int的函数
以下通过例子来展示怎样将函数指针与void*互相转换:
- 函数指针 => void*
1 2
int (*funcPtr)(int) = Func; // Func为符合该类型的函数 void* pTemp = reinterpret_cast<void*>(funcPtr);
- void* => 函数指针
1
int (*funcPtr)(int) =reinterpret_cast<int(*)(int)>(pTemp);
1.2 动态函数调用
函数指针是指向函数的指针。它可以用来存储函数的地址,然后通过这个指针调用函数。这在实现回调函数或函数表(如在策略模式中)时非常有用。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
void HelloWorld() {
std::cout << "Hello, World!" << std::endl;
}
int Add(int a, int b) {
return a + b;
}
int main() {
// 函数指针 fp 指向 HelloWorld 函数
void (*fp)() = HelloWorld;
fp(); // 通过函数指针调用 HelloWorld
// 函数指针 fp2 指向 Add 函数
int (*fp2)(int, int) = Add;
int result = fp2(3, 4); // 通过函数指针调用 Add
std::cout << "Result: " << result << std::endl;
return 0;
}
在这个例子中,fp 是一个指向 HelloWorld 函数的指针,而 fp2 是一个指向 Add 函数的指针。
1.3 回调函数
回调函数(Callback Function)是一个 通过函数指针传递给另一个函数的函数。它允许在一个函数内部调用另一个函数。回调函数是一种实现反向控制的技术,通常用于响应某些事件或处理异步操作。
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
void ProcessData(void (*callback)()) {
// ... 处理一些数据 ...
callback(); // 调用回调函数
}
void OnDataProcessed() {
std::cout << "Data processed." << std::endl;
}
int main() {
ProcessData(OnDataProcessed); // 将 OnDataProcessed 作为回调函数传递
return 0;
}
在这个例子中,OnDataProcessed 是一个回调函数,它被传递给 ProcessData 函数,并在 ProcessData 内部被调用。
另外,对于回调函数,也可以使用std::function来进行包装使用,具体例子见对应的文章。
1.4 函数指针数组
函数指针数组,就是函数指针的数组,就是一个存放函数指针的数组。
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
int Add(int x, int y){// 加法
return x + y;
}
int Sub(int x, int y){// 减法
return x - y;
}
int Che(int x, int y){// 乘法
return x * y;
}
int Div(int x, int y){// 除法
return x / y;
}
int main()
{
// 创建一个函数指针数组,将所有的算数函数放入数组:
int (*p[4])() = {Add,Sub,Che,Div};
int x = 3, y = 23;
for(int i = 0; i < 4; i++)
{
p[i](x, y); // 调用算数函数
}
return 1;
}
2. std::function
std::function是C++11标准库中增加的一个非常有用的工具。它提供了一种通用的、类型安全的函数包装器,可以容纳各种可调用对象,并在需要时进行调用。1
std::function位于<functional>头文件中。它是对C++语言中函数类型的通用封装,类似于函数指针,提供了一种机制来存储、传递和调用各种可调用对象(函数指针、函数对象、Lambda表达式、成员函数指针等)。通过使用std::function,我们可以将函数作为一种数据类型来处理,使得代码更加灵活和可扩展。
2.1 主要特性
- 函数签名和调用方式的灵活性:
std::function可以存储各种不同函数类型的对象,只要它们的参数类型和返回类型与std::function的模板参数匹配。这包括函数指针、函数对象、Lambda表达式、成员函数指针等。只要函数的签名(参数类型和返回类型)匹配,std::function可以用来存储和调用它们。 - 类型安全:
std::function提供了编译时类型检查,可以避免在运行时发生类型错误。如果试图将不兼容的函数或可调用对象赋值给std::function,编译器将抛出错误。 - 函数对象的拷贝和移动:
std::function可以拷贝和移动函数对象。这意味着我们可以在程序中传递和存储std::function对象,而不必担心对象的生命周期和所有权。 - 函数调用:通过使用
()运算符,我们可以调用存储在std::function中的函数对象,就像直接调用函数一样。std::function会根据存储的函数对象的类型自动调用正确的函数。
2.2 调用成员函数
下面是一个完整的例子,将成员函数的指针作为回调函数传递给另一个函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <functional>
// 在 MyClass 类中定义成员函数
class MyClass{
public:
void callback(){
std::cout << "callback called" << std::endl;
}
};
void SomeFunction(std::function<void()> func){
// 调用回调函数
func();
}
// 调用 SomeFunction,将成员函数作为回调函数传递
void main() {
std::function<void()> CallFunc = std::bind(&MyClass::callback, this);
SomeFunction(CallFunc);
}
2.3 调用其他函数
下面是一个简单的示例,展示了如何使用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
27
28
29
#include <iostream>
#include <functional>
int add(int a, int b){
return a + b;
}
struct Multiply{
int operator()(int a, int b){
return a * b;
}
};
int main(){
std::function<int(int, int)> func;
func = add; // 全局函数
std::cout << "Addition result: " << func(5, 3) << std::endl;
Multiply multiply;
func = multiply; // 函数对象
std::cout << "Multiplication result: " << func(5, 3) << std::endl;
auto lambda = [](int a, int b) { return a - b; };
func = lambda; // Lamda函数
std::cout << "Subtraction result: " << func(5, 3) << std::endl;
return 0;
}
2.4 std::function与函数指针的联系
std::function和函数指针都用于存储可调用对象(函数、函数对象、成员函数等),但它们有一些区别和联系。
- 区别:
- 类型灵活:
std::function可以用于存储任意可调用对象,包括函数指针、函数对象、成员函数指针、lambda表达式等,并提供一致的调用方法;而函数指针只能存储函数指针类型(一个指向函数的地址)。 - 对象管理:
std::function可以拷贝和移动,可以作为函数参数或返回值传递,而函数指针只是一个指向函数的指针,不能拷贝或移动。 - 多态支持:
std::function可以用于实现多态调用,即可以将不同的函数对象存储在同一个std::function对象中,并通过相同的调用方式来调用它们。函数指针不提供多态支持。
- 类型灵活:
- 联系:
- 调用:
std::function和函数指针可以通过调用运算符()来调用所存储的函数或函数对象。 - 转换:可以将函数指针转换为
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
#include <iostream>
#include <functional>
void foo() {
std::cout << "foo" << std::endl;
}
void bar(int x) {
std::cout << "bar: " << x << std::endl;
}
int main() {
std::function<void()> func1 = foo;
func1(); // 调用 foo
std::function<void(int)> func2 = bar;
func2(42); // 调用 bar
void (*funcPtr)() = foo;
funcPtr(); // 调用 foo,通过函数指针调用
return 0;
}
在上面的示例中,我们定义了两个函数foo和bar,分别无参和带一个整型参数。然后,我们使用std::function分别创建了存储无参函数和带参函数的对象,并通过调用运算符 () 调用它们。同时,我们也定义了一个函数指针funcPtr,并通过函数指针调用foo。
3. std::bind
当您需要将一个函数与其参数绑定以创建一个可调用的对象时(也就是一个std::function的对象),C++11标准库提供了std::bind函数模板。std::bind允许您将函数的一部分参数绑定,从而创建一个新的可调用对象,这个对象可以在稍后的时候以指定的参数来执行函数。1
3.1 std::bind的基本语法
1
auto boundFunc = std::bind(func, arg1, arg2, ...);
其中,func是待绑定的函数,arg1、arg2等是需要绑定的参数。
3.2 绑定普通函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <functional>
void printSum(int a, int b) {
std::cout << a + b << std::endl;
}
int main() {
// 下面auto的类型对应于std::function<void(int, int)>
auto boundFunc = std::bind(printSum, 10, std::placeholders::_1);
boundFunc(5); // 输出 15
return 0;
}
在这个示例中,我们绑定了函数printSum的第一个参数为10,而第二个参数使用占位符std::placeholders::_1表示稍后提供。然后,我们调用boundFunc并提供第二个参数为5,输出了15。
3.3 绑定成员函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <functional>
class MyClass {
public:
void printMessage(const std::string& message) {
std::cout << "Message: " << message << std::endl;
}
};
int main() {
MyClass obj;
auto boundFunc = std::bind(&MyClass::printMessage, &obj, std::placeholders::_1);
boundFunc("Hello"); // 输出 "Message: Hello"
return 0;
}
在这个示例中,我们定义了一个MyClass类,并创建了一个MyClass的对象obj。然后,我们使用std::bind绑定了obj的成员函数printMessage,并将obj作为第一个参数传递给std::bind。通过占位符std::placeholders::_1表示稍后提供的参数。最后,我们调用boundFunc并提供参数为”Hello”,输出了”Message: Hello”。
3.4 绑定函数对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <functional>
struct Adder {
int operator()(int a, int b) const {
return a + b;
}
};
int main() {
Adder add;
auto boundFunc = std::bind(add, std::placeholders::_1, 5);
int result = boundFunc(10); // result 的值为 15
std::cout << result << std::endl;
return 0;
}
在这个示例中,我们定义了一个函数对象Adder,它重载了函数调用运算符operator()用于实现加法操作。然后,我们创建了一个Adder对象add,并使用std::bind绑定了add的函数调用运算符,将std::placeholders::_1作为第一个参数,5作为第二个参数。最后,我们调用boundFunc并提供参数为10,得到了结果15。
需要注意的是,std::bind还支持其他一些特性,例如参数重排、常量绑定、函数指针绑定等,可以根据实际需求使用。此外,绑定的参数可以是左值引用或右值引用,也可以是std::cref或std::ref等引用包装器。更多关于std::bind的详细信息可以参考C++的相关文档和教程。
4. std::ref
std::ref 是 C++11 标准中引入的一个函数模板,用于将一个对象包装为一个引用。它可以让我们将一个对象作为引用传递给函数或算法,而不是按值传递,以实现对原始对象的直接修改。它在需要引用传递的情况下非常有用。1
std::ref 的作用是创建一个可以传递给接受引用参数的函数或算法的可调用对象。通过使用 std::ref,我们可以将对象传递给接受引用参数的函数,并确保在函数内部对该对象的操作能够影响到原始对象,而不是创建对象的副本。
4.1 常用于以下情况
- 作为函数或算法的参数传递:当函数或算法接受引用作为参数时,使用
std::ref可以将对象包装为引用,并将其传递给函数或算法。这样,函数或算法就能够直接修改原始对象,而不是创建对象的副本。 - 作为线程的参数传递:如果您使用线程库创建线程,并且想要在线程执行的函数中对某个对象进行修改,您可以使用
std::ref将对象作为引用传递给线程的函数。这样,线程函数就能够直接修改原始对象,而不是创建副本。 - 作为容器元素的引用:当您将对象存储在容器(如
std::vector或std::list)中,并且希望通过容器访问和修改对象时,可以使用std::ref将对象包装为引用,并将其添加到容器中。这样,容器中存储的是对象的引用,对容器中的引用进行操作会直接影响原始对象。
4.2 用法示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 需要多线程做的函数
void func(std::atomic<int>& progress, std::mutex& mutex)
{
std::lock_guard<std::mutex> lock(mutex);
++progress;
/*做点儿啥*/
}
int main()
{
std::atomic<int> progress(0);
std::mutex mutex;
std::jthread Thread(func, std::ref(progress), std::ref(mutex));
return 0;
}
上面的实例中,为了保证能将引用的参数正确传递给线程函数,使用了ref()函数。