在上一篇文章里,我们说到可以用 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()等函数中广泛使用 - 逻辑复杂时还是用具名函数更好