闭包递归的核心矛盾:匿名闭包如何在自己的函数体里指代自己。三种解法——命名引用(绑到外部变量)、自传递(把自己当参数 f(f, n))、不动点组合子(Y/Z combinator)。以斐波那契为例。
C
无闭包。普通函数有名字,天然递归:
1 2 3
| int fib(int n) { return n <= 1 ? n : fib(n - 1) + fib(n - 2); }
|
仅有函数指针时用自传递:
1 2 3 4 5
| typedef int (*rec_fn)(rec_fn, int); int fib(rec_fn self, int n) { return n <= 1 ? n : self(self, n - 1) + self(self, n - 2); } fib(fib, 10);
|
C++
命名引用(忌 auto,必须 std::function):
1 2 3
| std::function<int(int)> fib = [&](int n) -> int { return n <= 1 ? n : fib(n - 1) + fib(n - 2); };
|
自传递(零开销,C++14):
1 2 3 4
| auto fib = [](auto& self, int n) -> int { return n <= 1 ? n : self(self, n - 1) + self(self, n - 2); }; fib(fib, 10);
|
deducing this(最优雅,C++23,详见 Deducing this 专题):
1 2 3 4
| auto fib = [](this auto& self, int n) -> int { return n <= 1 ? n : self(n - 1) + self(n - 2); }; fib(10);
|
Java
命名引用(fib 赋值一次即 effectively final):
1
| Function<Integer, Integer> fib = n -> n <= 1 ? n : fib.apply(n - 1) + fib.apply(n - 2);
|
基本类型避免装箱:
1
| IntUnaryOperator fib = n -> n <= 1 ? n : fib.applyAsInt(n - 1) + fib.applyAsInt(n - 2);
|
Go
先 var 声明后赋值(不能 :=,左值在右侧求值后才生效):
1 2 3 4 5 6 7 8
| var fib func(int) int fib = func(n int) int { if n <= 1 { return n } return fib(n-1) + fib(n-2) } fmt.Println(fib(10))
|
Rust
闭包类型匿名且不能泛型(没有 C++ 的 auto& self),又不能像 Python 那样按名字延迟查找,所以 let fib = |n| { ... fib ... } 直接命名引用会报找不到 fib。三种解法里命名引用和自传递都可行,但都要 Rc 间接引用自身:
具名函数(无捕获时最简单,工程首选):
1 2 3
| fn fib(n: i32) -> i32 { if n <= 1 { n } else { fib(n - 1) + fib(n - 2) } }
|
命名引用(必须是闭包且要捕获状态时;占位再 swap,因为闭包构造时 fib 尚未绑定,RefCell 用于事后替换):
1 2 3 4 5 6 7 8 9 10 11 12 13
| use std::cell::RefCell; use std::rc::Rc;
let fib: Rc<RefCell<Box<dyn Fn(i32) -> i32>>> = Rc::new(RefCell::new(Box::new(|_| 0))); { let fib_inner = Rc::clone(&fib); let real = Box::new(move |n: i32| { if n <= 1 { n } else { fib_inner.borrow()(n - 1) + fib_inner.borrow()(n - 2) } }); *fib.borrow_mut() = real; } println!("{}", fib.borrow()(10));
|
自传递(用 newtype Recurse 打破类型递归——Rc<dyn Fn> 让具名类型可自引用,闭包把自己当参数传入;对应 C/C++ 的 self(self, ..)):
1 2 3 4 5 6 7 8
| use std::rc::Rc;
struct Recurse(Rc<dyn Fn(&Recurse, i32) -> i32>);
let fib = Recurse(Rc::new(|this: &Recurse, n: i32| -> i32 { if n <= 1 { n } else { (this.0)(this, n - 1) + (this.0)(this, n - 2) } })); println!("{}", (fib.0)(&fib, 10));
|
Python
def(最自然):
1 2
| def fib(n): return n if n <= 1 else fib(n - 1) + fib(n - 2)
|
lambda + 名字引用(按名字查找,调用时才解析):
1
| fib = lambda n: n if n <= 1 else fib(n - 1) + fib(n - 2)
|
不动点组合子(Z combinator,完全不依赖命名;Python 按值调用需 η-展开延迟求值):
1 2 3
| Z = lambda f: (lambda x: f(lambda v: x(x)(v)))(lambda x: f(lambda v: x(x)(v))) fib = Z(lambda f: lambda n: n if n <= 1 else f(n - 1) + f(n - 2)) print(fib(10))
|
变量捕获
闭包 = 函数代码 + 捕获的环境。捕获方式决定闭包能否修改外层变量,也影响递归写法(递归闭包要"引用到自己")。
| 语言 | 捕获方式 | 能否改外层变量 | 递归相关性 |
|---|
| C++ | [=]值 / [&]引用(显式) | [&] 或 mutable 可改 | [&] 引用 fib 自身 |
| Java | 按值(副本),须 effectively final | ❌ | 命名引用靠 final |
| Go | 按引用 | ✅ | 闭包按引用捕获 fib |
| Rust | Fn 借用 / FnMut 可变借用 / move 移动 | FnMut / move 可改 | 命名引用用 Rc<RefCell<..>> |
| Python | 捕获名字(非值) | 读可,重绑定需 nonlocal | 名字查找天然支持递归 |
C++:
1 2 3 4
| int x = 0; auto by_val = [x] { return x; }; auto by_ref = [&x] { x++; }; auto by_val_m = [x]() mutable { x++; };
|
Java:
1 2
| int x = 0; Function<Integer, Integer> f = n -> n + x;
|
Go:
1 2
| x := 0 f := func() { x++ }
|
Rust:
1 2 3
| let mut x = 0; let f = |n: i32| { x += n; }; let g = move || { x };
|
Python:
1 2 3 4 5 6 7 8
| x = 0 f = lambda n: n + x
def outer(): x = 0 def h(): nonlocal x x += 1
|
横向对比
| 语言 | 命名引用法 | 自传递法 | 不动点组合子 | 直接 let f = |..|{..f..} |
|---|
| C | 函数名即可 | 函数指针 self(self,...) | 不常用 | 无闭包 |
| C++ | std::function + [&](忌 auto) | 泛型 lambda self(self,...) | 可写 | ❌ 类型推导失败 |
| Java | Function / IntUnaryOperator | 需自定义接口,不实用 | 可写,繁琐 | ❌(需显式类型) |
| Go | var 先声明后赋值 | 可用 | 不常用 | ❌ undefined: fib |
| Rust | 需 Rc<RefCell<..>>,繁琐 | newtype + Rc 自传递 | 可写,繁琐 | ❌ 找不到 fib |
| Python | fib = lambda ... 名字查找 | 可用 | Z combinator 经典 | ✅ 名字延迟查找 |