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 的方式侵入性很小用起來很舒服。第一種方案寫起來沒有什麼靈活性,太過於繁瑣囉嗦,可是是最穩的方式,這麼寫沒人能夠指責你。