C 扩展对闭包特性的支持

c/c++

浏览数:40

2019-10-7

今日听说某君批评 C 语言说它【输入一个参数返回一个函数】很困难。

例如在 Python 中,你可以

def addn(n):
  def addx(x):
    return n + x
  return addx
  
my_adder = addn(42)
print(my_adder(21))

标准 C 要实现这样的功能也不是不可以,这是一个闭包的功能,我们只要显式的把上下文抓住就可以了。

#include <stdio.h>

typedef struct Adder { int n; } Adder;

Adder addn(int n) {
  Adder addn;
  addn.n = n;
  return addn;
}

int apply(Adder addn, int x) { return addn.n + x; }

int main() {
  Adder add42 = addn(42);
  printf("%d\n", apply(add42, 21));

  return 0;
}

这样的写法未免过于复杂,而闭包是现代程序设计里面很常用到的一种结构,面对更复杂的上下文,为了模拟闭包,所要做的比一个 Adder 结构体要多得多。对于这样常用的结构,主流的 C 编译器,即 gcc 和 clang 都提供了 C 扩展来支持这样的功能。

在 clang 中,你可以这样写

#include <stdio.h>
#include <Block.h>

int (^addn(int n))(int) {
  __block int i = n;

  return Block_copy( ^(int x) {
    return x + n;
  });
}

int main() {
  int (^add42)(int) = addn(42);
  printf("%d\n", add42(21));
  
  return 0;
}

熟悉 Objective-C 的同学应该对 block 不陌生,这里 block 的类型名写起来比较麻烦,可以用 typedef int (^MyClosure)(int); 来定义更有可读性的类型名。

在 gcc 中,可以使用 nested functions 扩展

#include <stdio.h>

int (*addn(int n))(int) {
  int addx(int x) {
    return n + x;
  }

  return addx;
}

int main() {
  int (*add42)(int) = addn(42);
  printf("%d\n", add42(21));

  return 0;
}

同样这里的函数指针也可以用 typedef 来定义一个可读的类型名。

这个问题在 C++ 中可以通过在类中重载括号运算符来实现。本质上是上面复杂代码所揭示出的捕获上下文并查看上下文信息的动作,现代语言为此封装了看起来像直接调用的简洁语法,使得代码更好写,更好读。

作者:tisonkun