C 擴展對閉包特性的支持

今日聽說某君批評 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++ 中可以通過在類中重載括號運算符來實現。本質上是上面複雜代碼所揭示出的捕獲上下文並查看上下文信息的動作,現代語言爲此封裝了看起來像直接調用的簡潔語法,使得代碼更好寫,更好讀。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章