为什么有了函数指针还有 Functional
- 1: 函数指针定义
- 2: 函数指针结论
- 3:疑问
- 4: Function来源
- 5:Functional 特点
1: 函数指针定义
在C++中可以使用指针指向一段代码,这个指针就叫函数指针,假设有下面一段代码
#include<iostream>
int func(int a) {
return a+1;
}
void main()
{
// 定义一个函数,然后使用指针变量f指向该函数
int (*f)(int) = func;
printf("%p\n",f);
}
我们编译上述代码,得到 汇编和机器码
💚💚💚
0x401156
得到的地址为:这个地址正好是 编译好的函数 func 所在的地址 : ox401156
而函数指针打印的地址也是这个。
2: 函数指针结论
实际上函数指针本质也是一个指针,只不过这个指针指向的不是内存中的一段数据而是内存中的一段代码。
🧡🧡🧡
- 我们常说的指针一般都是指向内存中的一段数据,而函数指针指向了内存中的一段代码
- 在这个示例中指向了内存地址 ox401156
- 在这个地址中保存了函数func的机器指令。
3:疑问
为什么编译器在生成可执行文件时就知道函数func存放在内存地址0x400526上呢?这不应该是程序被加载到内存后开始运行时才能确定的吗?
4: Function来源
函数指针的作用是可以把一段代码当做一个变量传来传去,它的主要作用就是回调函数。
如:关于回调函数其实是在A模块定义,在B模块被调用,就像这样。
这是一个很简单的场景,但是我们设想一下,有如下场景:
我们需要在模块A 中定义函数,同时函数A的运行需要依赖B模块,然后将模块A的函数和 模块B的数据一并传给 C模块来调用,就像这样 :
- 很显然,现在单纯的函数指针已经不够用了,因为函数指针只是单纯的指向了内存中的一段代码,我们不但需要将内存中的一段代码同时也需要将内存中的一块数据传递给 模块C。
- 那么此时 你就可以定义一个结构体,将代码和数据打包起来。
typedef void (*func) (int);
struct closure
{
func f;
int arg;
}
- 在这里,我们将这个结构体命名为 closure,注意看,这个结构体汇总有两部分
- 一个指向代码的指针变量
- 一个保存数据的变量
- 所以,我们在A模块 为指针变量赋值,在B模块为保存数据的变量赋值,然后将此结构体传递给模块C,模块C就可以这样使用。
void run(struct closure func)
{
func->f(func->arg);
}
// 即,closure 既包含了一段代码也包含了这段代码使用的数据,这里的数据也被称为 context(上下文)或者
// environment(环境), 其实不管怎么称呼,它就是函数运行依赖的数据。
5:Functional 特点
上述步骤中,这正是 std::functional 的目的所在。
- 事实上,单纯的函数指针并没有捕捉上下文的能力,这里的上下文就是 指 代码依赖的数据,你不得不自己动手构造出一个结构体用来存储代码依赖的上下文。
- 在C++中你也没办法单纯的利用函数指针指向对象的成员函数,就是因为函数指针没有办法捕捉this(指向对象的指针)这个上下文。
- std::function 的作用本质上和我们 刚才定义的结构体区别不大。
- 但是为了使 std::function 更加通用,你可以用其存储任何可以被调用的对象(callable object),如:函数指针,成员函数,仿函数,Lambda表达式