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() 等函数中广泛使用
  • 逻辑复杂时还是用具名函数更好
相关推荐
曹牧15 分钟前
C#:字符串占位符与值数量不一致
开发语言·数据库·c#
迅猛龙办公室23 分钟前
Python函数实现反转一个只有3位数的整数
开发语言·前端·python
“AI国潮设计-小江”26 分钟前
《【AIGC实战】Python+SDXL批量生成“英歌舞树莓香草布丁”IP,附保持角色一致性的代码逻辑》
开发语言·人工智能·python·prompt·aigc
闻缺陷则喜何志丹32 分钟前
【C++特性及类库】多线程
开发语言·c++·多线程·语言特性·系统库
新手小新39 分钟前
通信工程师学习笔记5-通信系统
笔记·学习
dtq042444 分钟前
C++3 - 分支语句if和string变量
开发语言·c++·学习
jimy11 小时前
c++类成员访问只看静态类型
开发语言·c++
鸽芷咕1 小时前
把 SQL 编辑器用顺:KSQL Developer 从补全到事务的实战笔记
笔记·sql·编辑器
156082072191 小时前
银河麒麟 Qt 可执行文件打包说明
开发语言·驱动开发·qt
SEO_juper1 小时前
手写 llms.txt 验证器:让 AI 更愿意引用你的网站(GEO)
开发语言·人工智能·python·聚类·seo·独立站