AI Library · 知识库

# 闭包与捕获:固定阈值过滤器

学习目标:掌握闭包对外部词法环境的捕获机制,理解按值捕获、按引用捕获以及闭包在堆栈上的生命周期开销。

前置知识与范围:高阶函数与基本闭包语法。本篇通过构造带动态阈值的数字过滤器工厂,演示状态捕获与调用约束。

# 问题与边界

实现一个闭包工厂函数 make_threshold_filter(threshold):

  1. 接收一个比较阈值 threshold(如 10)。
  2. 返回一个谓词函数(Predicate Closure),该函数接收一个数值 x,并在 x >= threshold 时返回 true,否则返回 false。
  3. 状态绑定契约:返回的闭包必须将创建时的 threshold 完整捕获封装在自身内部;当外层函数栈帧退出后,该闭包依然能够长期有效调用。

测试用例:

# 关键差异

语言 闭包捕获机制 跨作用域堆逃逸处理 闭包可调用约束形态
Rust 显式 move 关键字转移所有权 返回闭包必须 Box<dyn Fn(i32) -> bool> 或 impl Fn Fn(不可变借用)、FnMut(可变借用)、FnOnce(消耗所有权)
Go 闭包隐式捕获外部变量引用 编译器逃逸分析(Escape Analysis)自动将捕获变量分配到堆上 一等公民函数类型 func(int) bool
Java 隐式捕获局部变量 捕获变量必须是 有效不可变(effectively final) java.util.function.IntPredicate
JavaScript 词法作用域闭包链(Lexical Scope) V8 垃圾回收器自动维持外层作用域闭包上下文 一等公民函数对象
TypeScript 沿用 JS 机制 闭包上下文堆保持 强签名 (x: number) => boolean
Kotlin 捕获外部变量 若闭包修改外部变量,Kotlin 自动包装为 Ref 堆对象 函数类型 (Int) -> Boolean
Python 词法作用域(Cell 对象) 作用域链持有引用;写入需声明 nonlocal Callable[[int], bool]
C++ 显式捕获列表 [threshold] 必须按值捕获 [=] 或 [threshold];按引用捕获在退出作用域后会崩溃! std::function<bool(int)>

# 各语言实现

# Rust

Rust// 使用 move 关键字将 threshold 的所有权移动进闭包内部,
// 这样当 make_threshold_filter 函数返回后,闭包依然安全有效
fn make_threshold_filter(threshold: i32) -> impl Fn(i32) -> bool {
    move |x| x >= threshold
}

fn main() {
    let filter10 = make_threshold_filter(10);

    assert!(filter10(15));
    assert!(filter10(10));
    assert!(!filter10(5));

    println!("filter10(15): {}", filter10(15));
}

Rust 的 move 关键字强制闭包按值获取环境变量,彻底消除了返回闭包指向已销毁局部栈帧的危险。

# Go

Gopackage main

import "fmt"

func MakeThresholdFilter(threshold int) func(int) bool {
	return func(x int) bool {
		return x >= threshold // 隐式捕获外部 threshold
	}
}

func main() {
	filter10 := MakeThresholdFilter(10)

	if !filter10(15) || !filter10(10) || filter10(5) {
		panic("mismatch")
	}

	fmt.Printf("filter10(15): %t\n", filter10(15))
}

Go 编译器的逃逸分析会自动检测到 threshold 被逃逸闭包所引用,从而将其分配在堆上而非当前函数的栈帧上。

# Java

Javaimport java.util.function.IntPredicate;

public class Main {
    public static IntPredicate makeThresholdFilter(int threshold) {
        // threshold 在此必须是 effectively final
        return x -> x >= threshold;
    }

    public static void main(String[] args) {
        IntPredicate filter10 = makeThresholdFilter(10);

        assert filter10.test(15);
        assert filter10.test(10);
        assert !filter10.test(5);

        System.out.println("filter10(15): " + filter10.test(15));
    }
}

Java 强制要求闭包引用的外部局部变量不能被二次修改,这避免了复杂的并发可见性竞争。

# JavaScript

JavaScriptconst assert = {
  equal: (a, b) => { if (a !== b) throw new Error(`${a} !== ${b}`); },
  deepEqual: (a, b) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${JSON.stringify(a)} !== ${JSON.stringify(b)}`); },
  throws: (fn, errType) => {
    try { fn(); throw new Error("expected throw"); } catch (e) {
      if (e.message === "expected throw") throw e;
      if (errType && !(e instanceof errType)) throw e;
    }
  }
};

function makeThresholdFilter(threshold) {
  return (x) => x >= threshold;
}

const filter10 = makeThresholdFilter(10);

assert.equal(filter10(15), true);
assert.equal(filter10(10), true);
assert.equal(filter10(5), false);

console.log(`filter10(15): ${filter10(15)}`);

JavaScript 原生采用词法作用域,返回的箭头函数自动闭合保存其创建时的作用域链。

# TypeScript

TypeScriptconst assert = {
  equal: (a: unknown, b: unknown) => { if (a !== b) throw new Error(`${a} !== ${b}`); },
  deepEqual: (a: unknown, b: unknown) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${JSON.stringify(a)} !== ${JSON.stringify(b)}`); },
  throws: (fn: () => void, errType?: unknown) => {
    try { fn(); throw new Error("expected throw"); } catch (e: unknown) {
      if (e instanceof Error && e.message === "expected throw") throw e;
      if (typeof errType === "function" && !(e instanceof errType)) throw e;
    }
  }
};

function makeThresholdFilter(threshold: number): (x: number) => boolean {
  return (x: number): boolean => x >= threshold;
}

const filter10 = makeThresholdFilter(10);

assert.equal(filter10(15), true);
assert.equal(filter10(10), true);
assert.equal(filter10(5), false);

console.log(`filter10(15): ${filter10(15)}`);

TypeScript 为返回的函数签名标注明确的参数与返回值类型。

# Kotlin

Kotlinfun makeThresholdFilter(threshold: Int): (Int) -> Boolean {
    return { x -> x >= threshold }
}

fun main() {
    val filter10 = makeThresholdFilter(10)

    check(filter10(15))
    check(filter10(10))
    check(!filter10(5))

    println("filter10(15): ${filter10(15)}")
}

Kotlin 的闭包语法高度紧凑,直接返回一个函数字面量。

# Python

Pythonfrom typing import Callable

def make_threshold_filter(threshold: int) -> Callable[[int], bool]:
    def predicate(x: int) -> bool:
        return x >= threshold
    return predicate

if __name__ == "__main__":
    filter10 = make_threshold_filter(10)

    assert filter10(15) is True
    assert filter10(10) is True
    assert filter10(5) is False

    print(f"filter10(15): {filter10(15)}")

Python 函数是一等公民,内部嵌套函数访问外层变量形成标准闭包。

# C++

C++#include <iostream>
#include <functional>
#include <cassert>

// 必须显式 [threshold] 按值捕获!
// 若误写为 [&threshold],返回后引用的局部栈变量已销毁,调用将导致段错误未定义行为!
std::function<bool(int)> make_threshold_filter(int threshold) {
    return [threshold](int x) {
        return x >= threshold;
    };
}

int main() {
    auto filter10 = make_threshold_filter(10);

    assert(filter10(15));
    assert(filter10(10));
    assert(!filter10(5));

    std::cout << std::boolalpha << "filter10(15): " << filter10(15) << "\n";
}

C++ 赋予了程序员精细的捕获控制权:按值捕获 [threshold] 会在闭包对象内部复制一份数据,保证逃逸后的安全。

# 迁移误区与适用限制

# 运行与验证

语言版本基线见示例基线。 所有程序运行后控制台输出均为:

纯文本filter10(15): true

闭包跨作用域调用、临界值比对与状态保持测试全部通过。

# 相关阅读