GDScript 入门笔记(十一):Lambda 与匿名函数

在上一篇文章里,我们说到可以用 connect() 连接信号时直接写一个函数名,或者定义一个具名函数再连接。但有时候,回调函数只在一个地方使用,单独为它写一个函数有点浪费,代码也显得零散。这时候就可以用 Lambda 表达式,也就是匿名函数------没有名字的函数,直接写在需要的地方。

一、什么是 Lambda

Lambda 就是一个没有名字的函数,可以赋值给变量、作为参数传给其他函数、或者直接写在调用处。它本质上和普通函数一样,只是少了正式的函数声明。

在 GDScript 4 里,Lambda 的语法如下:

gdscript 复制代码
func(参数列表):
    # 函数体
    return 返回值  # 可选

如果没有参数,括号里留空。Lambda 可以写多行,也可以简化为单行。

二、基本语法与简单例子

赋值给变量

gdscript 复制代码
var square = func(x):
    return x * x

print(square(5))  # 25

这里的 square 是一个变量,指向一个匿名函数,可以像调用普通函数一样使用它。

单行写法

如果函数体只有一行,可以直接写在冒号后面:

gdscript 复制代码
var add = func(a, b): return a + b
print(add(3, 4))  # 7

单行写法简洁,但可读性稍差,适合简单逻辑。多行的时候还是用缩进块。

立即调用

Lambda 也可以定义后立刻调用,不过这种用法很少见:

gdscript 复制代码
var result = (func(x): return x * 2)(10)
print(result)  # 20

三、Lambda 与普通函数的区别

Lambda 本质上就是函数,但有一些细微区别:

  • Lambda 没有名字,定义后不会在类中注册为方法,所以不能通过 self.方法名 调用,只能通过变量引用。
  • Lambda 可以捕获外部作用域的变量(闭包,后面讲)。
  • Lambda 的参数和返回值类型可以省略,也可以显式标注。
gdscript 复制代码
var multiply: Callable = func(a: int, b: int) -> int:
    return a * b

Callable 是函数引用的类型,可以把 Lambda 存到 Callable 变量里。

四、作为参数传递

Lambda 最常见的使用场景是作为参数传递给其他函数,特别是那些需要回调的函数。比如数组的 map()filter() 方法,它们接受一个函数作为参数,对数组元素进行处理。

map():对每个元素应用函数

gdscript 复制代码
var numbers = [1, 2, 3, 4]
var doubled = numbers.map(func(n): return n * 2)
print(doubled)  # [2, 4, 6, 8]

map() 遍历数组,对每个元素调用传入的 Lambda,把返回值组成新数组返回。原数组不变。

filter():按条件筛选元素

gdscript 复制代码
var numbers = [1, 2, 3, 4, 5, 6]
var evens = numbers.filter(func(n): return n % 2 == 0)
print(evens)  # [2, 4, 6]

filter() 只保留 Lambda 返回 true 的元素。

这两个方法在数据处理时非常有用,避免了写显式循环,代码更简洁。

五、与信号配合

信号连接时,如果回调逻辑很简单,用 Lambda 可以省去单独定义函数。

gdscript 复制代码
button.pressed.connect(func():
    print("按钮被按下")
)

如果信号带参数,Lambda 也要写出对应参数:

gdscript 复制代码
player.health_changed.connect(func(new_health):
    print("血量变为:", new_health)
)

这种写法让信号连接和回调逻辑放在一起,阅读时更直观。不过如果回调逻辑较复杂,还是建议用具名函数,便于调试和维护。

六、闭包

闭包是指 Lambda 可以"记住"定义时所在作用域里的变量。即使 Lambda 在别处被调用,它仍然能访问那些变量。

gdscript 复制代码
var counter = 0
var increment = func():
    counter += 1

increment()
increment()
print(counter)  # 2

这里 increment 捕获了外部变量 counter,每次调用都会修改它。这在普通函数里做不到,因为普通函数不能修改外部局部变量。

闭包的陷阱:循环中的变量捕获

一个常见的坑是,在循环中创建多个 Lambda,它们会共享同一个循环变量。

gdscript 复制代码
var funcs = []
for i in range(3):
    funcs.append(func(): return i)

for f in funcs:
    print(f.call())

你可能期望输出 0 1 2,但实际输出 2 2 2。因为 Lambda 捕获的是变量 i 本身,而不是它的值。当循环结束后,i 变成了最后的值 2,所以所有 Lambda 都返回 2。

解决办法是在每次迭代中创建一个新的局部变量来捕获当前值:

gdscript 复制代码
for i in range(3):
    var captured = i
    funcs.append(func(): return captured)

这样每个 Lambda 捕获的是不同的 captured 变量,就能得到 0 1 2。这个坑在很多语言里都存在,值得注意。

七、Lambda 与 Callable 类型

在 GDScript 中,Lambda 和函数引用都属于 Callable 类型。你可以把 Lambda 赋值给 Callable 变量,也可以把具名函数赋值给 Callable

gdscript 复制代码
var func_ref: Callable

func my_func():
    print("hello")

func _ready():
    func_ref = my_func
    func_ref.call()   # hello

    func_ref = func(): print("world")
    func_ref.call()   # world

Callable.call() 可以调用它,也可以使用 func_ref() 直接调用(GDScript 4 支持)。理解 Callable 有助于处理函数引用和动态调用。

八、Lambda 的一些限制和注意事项

  • 不能写多语句的单行 Lambda:如果需要多行,必须用缩进块。
  • Lambda 不能有默认参数(GDScript 4 的限制):你可以在 Lambda 内部处理默认值。
  • 闭包变量是引用:注意上面循环捕获的问题。
  • Lambda 在 map/filter 中通常是同步执行,不要在里面做耗时操作。
  • 过度使用 Lambda 可能降低可读性:如果逻辑复杂,还是写具名函数比较好。

九、实际例子:用 Lambda 简化 UI 按钮回调

假设有多个按钮,每个按钮点击后需要设置不同文本。传统方法要写多个函数,用 Lambda 可以更简洁:

gdscript 复制代码
extends Node

@onready var button_1 = $Button1
@onready var button_2 = $Button2

func _ready():
    button_1.pressed.connect(func(): _on_button_pressed("按钮1"))
    button_2.pressed.connect(func(): _on_button_pressed("按钮2"))

func _on_button_pressed(text: String):
    print(text + " 被按下")

或者更直接:

gdscript 复制代码
button_1.pressed.connect(func(): print("按钮1被按下"))
button_2.pressed.connect(func(): print("按钮2被按下"))

这比定义两个独立函数要简洁得多。

十、总结

Lambda 是 GDScript 中很有用的特性,尤其适合回调、数据处理和需要闭包的场景。掌握 Lambda 可以让代码更紧凑,但也要注意可读性和闭包陷阱。

重点回顾:

  • Lambda 语法:func(参数): 返回值
  • 可以赋值给变量、作为参数传递、与信号配合
  • 闭包可以捕获外部变量,但循环中要注意捕获的是变量引用
  • map()filter() 等函数中广泛使用
  • 逻辑复杂时还是用具名函数更好
相关推荐
摇滚侠1 小时前
《SpringBoot 3:入门与应用实战》第 12 章 JDBC 与事务 使用 JdbcTemplate 阅读笔记 32
spring boot·笔记·后端
事圆则缓3 小时前
Kotlin 协程完全指南
开发语言·kotlin
鱼子星_3 小时前
【C++】继承和多态(上)
c++·笔记
我爱写代码i3 小时前
BeLink - 支持生成多种URL 缩短网址PHP源码
开发语言·php·短网址php源码
Java后端的Ai之路3 小时前
20、Python - 备忘录模式
开发语言·人工智能·python·外观模式·备忘录模式
xian_wwq4 小时前
【学习笔记】深度认知系列-第14讲 端侧AI崛起——为什么AI正在从云端走向本地
人工智能·笔记·学习
统计学小王子5 小时前
数学建模国赛倒计时4天——《统计模型精讲(混合效应模型R语言实现)》
开发语言·数学建模·r语言
知无不研5 小时前
c语言中循环的介绍与简单应用
c语言·开发语言·算法·循环·for·while
郝学胜-神的一滴5 小时前
Qt 高级编程 045:坐标体系深度实战
开发语言·c++·windows·python·qt·程序人生