闭包递归的核心矛盾:匿名闭包如何在自己的函数体里指代自己。三种解法——命名引用(绑到外部变量)、自传递(把自己当参数 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); // 55

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))); // placeholder
{
let fib_inner = Rc::clone(&fib);
let real = Box::new(move |n: i32| { // build the real closure
if n <= 1 { n } else { fib_inner.borrow()(n - 1) + fib_inner.borrow()(n - 2) }
});
*fib.borrow_mut() = real; // then swap it in
}
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)) # 55

变量捕获

闭包 = 函数代码 + 捕获的环境。捕获方式决定闭包能否修改外层变量,也影响递归写法(递归闭包要"引用到自己")。

语言捕获方式能否改外层变量递归相关性
C++[=]值 / [&]引用(显式)[&]mutable 可改[&] 引用 fib 自身
Java按值(副本),须 effectively final命名引用靠 final
Go按引用闭包按引用捕获 fib
RustFn 借用 / 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++; }; // 按引用,可改外部 x
auto by_val_m = [x]() mutable { x++; }; // 副本可改(不影响外部)

Java:

1
2
int x = 0;                                      // 须 effectively final
Function<Integer, Integer> f = n -> n + x; // 捕获 x 的副本,不可修改 x

Go:

1
2
x := 0
f := func() { x++ } // 按引用捕获,可改外部 x

Rust:

1
2
3
let mut x = 0;
let f = |n: i32| { x += n; }; // FnMut: 可变借用,可改 x
let g = move || { x }; // move: 按值移动捕获,拥有 x

Python:

1
2
3
4
5
6
7
8
x = 0
f = lambda n: n + x # 捕获名字,运行时查找;读外部 x 没问题

def outer():
x = 0
def h():
nonlocal x # 声明后才可重绑定外层 x
x += 1

横向对比

语言命名引用法自传递法不动点组合子直接 let f = |..|{..f..}
C函数名即可函数指针 self(self,...)不常用无闭包
C++std::function + [&](忌 auto泛型 lambda self(self,...)可写❌ 类型推导失败
JavaFunction / IntUnaryOperator需自定义接口,不实用可写,繁琐❌(需显式类型)
Govar 先声明后赋值可用不常用undefined: fib
RustRc<RefCell<..>>,繁琐newtype + Rc 自传递可写,繁琐❌ 找不到 fib
Pythonfib = lambda ... 名字查找可用Z combinator 经典✅ 名字延迟查找