Closure function 说白了就是嵌套函数能够访问函数定义是当时的 scope 的变量。html
先看下面一段话:前端
Nested function is not supported by C because we cannot define a function within another function in C. We can declare a function inside a function, but it’s not a nested function. Because nested functions definitions can not access local variables of the surrounding blocks, they can access only global variables of the containing module. This is done so that lookup of global variables doesn’t have to go through the directory. As in C, there are two nested scopes: local and global (and beyond this, built-ins). Therefore, nested functions have only a limited use. If we try to approach nested function in C, then we will get compile time error.bash
大概意思是说,C 中不能够在函数内部定义函数,就算咱们在函数内声明了一个函数,可是那也不算是嵌套函数。说到这里你大概会想到的实现方式会是这个样子:闭包
#include <stdio.h>
typedef void (*func_t)();
void func_test(int i) {
printf("%d\n", i);
}
func_t closure(int i) {
return func_test;
}
int main(int argc, char const *argv[]) {
func_t cb = closure(1);
cb(1);
cb(1);
return 0;
}
复制代码
这样在函数内声明的方式,实际上是没有权限访问当前定义域内的变量的,你能够按照你的想法这个基础上随便怎么修改,都无济于事。app
用 C 的 struct 来实现,C 的 struct 内部是能够定义函数的,而后咱们在函数中传入当前的 context,其实也算是曲折的方式实现了 closure 的特性。看代码会一目了然:ide
#include <stdio.h>
#include <stdlib.h>
struct func_struct {
int x;
void (*call)(struct func_struct *);
};
void func_call(struct func_struct *f) {
f->x++;
printf("%d\n", f->x);
}
struct func_struct *closure(int x) {
struct func_struct *func = (struct func_struct *)malloc(sizeof(struct func_struct));
func->x = x;
func->call = func_call;
return func;
}
int main(int argc, char const *argv[]) {
struct func_struct *f = closure(1);
f->call(f);
f->call(f);
f->call(f);
free(f);
return 0;
}
复制代码
代码的输出结果也是如预期同样,是 234 依次输出。为了用 closure 的特性,咱们须要把闭包中全部的变量封装到 struct 中,其实用起来并非那么舒服。函数
说点题外话,不少人说 Go,Rust,C 有点像,其实在 struct method 这个地方时很像的。看下面三段代码:ui
#include <stdio.h>
#include <stdlib.h>
struct test {
void (*method)();
};
void method() {
printf("call method\n");
}
int main(int argc, char const *argv[]) {
struct test *t = (struct test *)malloc(sizeof(struct test));
t->method = method;
t->method();
return 0;
}
复制代码
package main
type test struct{}
func (t test) method() {
println("call method")
}
func main() {
var t test
t.method()
}
复制代码
struct Test {}
impl Test {
pub fn method() {
println!("call method");
}
}
fn main() {
Test::method();
}
复制代码
Go 和 Rust 实现起来在代码行数上都不多,很简洁,很现代化。this
这个方案里边用到的不是 C 自己的特性了,咱们用编译器 Clang 自己的特性,Clang 是 LLVM 的编译前端,LLVM 中有这么一个 block
的特性,那么 Clang 固然也能够用这个特性,看这里,有一些介绍。Talk is cheap,show me the code:spa
#include <Block.h>
#include <stdio.h>
typedef int (^IntBlock)();
IntBlock MakeCounter(int start, int increment) {
__block int i = start;
return Block_copy(^{
int ret = i;
i += increment;
return ret;
});
}
int main(void) {
IntBlock mycounter = MakeCounter(5, 2);
printf("First call: %d\n", mycounter());
printf("Second call: %d\n", mycounter());
printf("Third call: %d\n", mycounter());
/* 因为是复制的块,所以须要释放 */
Block_release(mycounter);
return 0;
}
复制代码
这段代码用 GCC 是编译不过去的,须要用 Clang 来编译。须要加上额外的参数 clang -fblocks test.c
,将会生成 a.out
。
GCC 里边有没有相对应的特性呢?答案是有的。
#include <stdio.h>
int foo(int a) {
int square(int z) { return z * z; }
return square(a);
}
int main(void) {
printf("%d\n", foo(2));
return 0;
}
复制代码
这段代码须要这么编译 gcc -std=c11 test.c
,Clang 是编译不经过的,用到的是 GCC 单独为此作的扩展,详情看这里。那么咱们最开始的那种方式来实现 closure,也是 OK,只不过要把函数定义在函数内部就 OK 了。
方案中后两种都是利用编译器的特性来作的,有点不是特别好,可是编译器既然有这个特性咱们为什么不能够用呢?存在即合理。我的以为 GCC 的方式侵入性很小用起来很舒服。第一种方案写起来没有什么灵活性,太过于繁琐啰嗦,可是是最稳的方式,这么写没人能够指责你。