为什么闭包能共享变量 pre

在提供的代码中,闭包能够共享变量 pre 的关键在于 词法作用域(Lexical Scoping)闭包捕获变量 的特性:

1
2
3
4
5
6
7
8
9
10
11
func isValidBST(root *TreeNode) bool {
pre := math.MinInt64 // 外层函数变量

var dfs func(*TreeNode) bool
dfs = func(node *TreeNode) bool {
// 这里可以访问并修改外层的 pre 变量
if node.Val <= pre { ... }
pre = node.Val
}
return dfs(root)
}

工作原理:

  1. 词法作用域

    • Go 中函数可以访问定义时所在作用域的所有变量
    • dfs 匿名函数定义在 isValidBST 函数体内,自然能访问 pre
  2. 闭包捕获变量

    • 闭包不会复制变量值,而是捕获变量的引用
    • 所有递归调用的 dfs 都指向同一个 pre 变量
    • 修改 pre 会影响所有闭包实例
  3. 递归共享状态

    1
    2
    3
    4
    5
    6
    7
    graph LR
    A[pre=MinInt64] --> B[dfs-根节点]
    B --> C[dfs-左子节点]
    C --> D[dfs-左孙节点]
    D -->|更新pre| C
    C -->|更新pre| B
    B -->|更新pre| E[dfs-右子节点]

闭包名称的含义与起源

名称来源

“闭包”(Closure)一词来源于 函数”闭合”(close over)其定义环境 的概念:

  • 最早由 Peter J. Landin 在 1964 年提出
  • 源自数学中的 λ演算(Lambda Calculus)闭包属性(Closure Property)
  • 在编译原理中称为 词法闭包(Lexical Closure)

名称含义

术语解释
闭(Close)函数”封闭”地携带其定义时的环境
包(-ure)将函数与其环境”打包”在一起

核心特性

  1. 环境捕获:函数记住并访问定义时的作用域
  2. 状态持久化:捕获的变量生命周期与闭包相同
  3. 私密状态:只有闭包能访问捕获的变量

闭包在 BST 验证中的优势

1
2
3
4
pre := math.MinInt64  // 共享状态
dfs := func(node *TreeNode) bool {
// 递归过程共享同一个 pre
}
  1. 状态管理简化:无需指针传递
  2. 代码更简洁:减少参数传递
  3. 逻辑更直观:直接读写共享状态
  4. 避免值复制:高效处理递归状态

闭包的本质

闭包本质上是一个 (函数 + 绑定环境) 的组合体:

1
2
3
4
5
6
type Closure struct {
F func(*TreeNode) bool // 函数逻辑
Env struct { // 捕获的环境
pre int
}
}

每次递归调用时,都使用同一个 Env 环境,这正是 BST 验证需要的中序遍历状态一致性保证。

闭包 vs 指针参数

特性闭包指针参数
状态共享自动捕获需显式传递
可读性★★★☆☆★★☆☆☆
安全性变量封装暴露内存地址
递归适用性非常适合需要小心指针传递

在 BST 验证场景中,闭包方案更简洁安全,避免了指针传递的复杂性,是 Go 语言处理递归状态的首选模式。