Menu
CoddyTech

自定义迭代器

本课属于 Coddy GDScript 学习旅程的“面向对象编程”单元。第 33 课,共 65 课。

Python 可以通过 __len__、__getitem__、__contains__ 和 __iter__ 等魔术方法,让类的行为像列表一样。Godot 只有一种虚拟方法可以实现其中一项功能,也就是 for 循环。以下三个方法可以让 for item in my_object: 正常运行:

  • _iter_init(iter) 在第一轮开始前运行一次。它会设置起始位置,如果有第一个元素,就返回 true。
  • _iter_get(position) 返回当前位置的元素,循环变量会获得这个值。
  • _iter_next(iter) 在每一轮后续循环开始前运行。它会移动到下一个位置,没有更多元素时返回 false,从而结束循环。
# team.gd
class_name Team
extends RefCounted

var _members: Array[String] = []

func add(member: String) -> void:
	_members.append(member)

func _iter_init(iter: Array) -> bool:
	iter[0] = 0
	return iter[0] < _members.size()

func _iter_next(iter: Array) -> bool:
	iter[0] += 1
	return iter[0] < _members.size()

func _iter_get(position):
	return _members[position]

iter 是 Godot 为每个循环创建的一个包含一个槽位的数组。iter[0] 保存该循环的位置,Godot 会将它的值传递给 _iter_get()。现在,Team 可以在 for 循环中使用:

var team := Team.new()
team.add("Ada")
team.add("Bo")
for member in team:
	print(member) # 先是 Ada,然后是 Bo

将位置保存在 iter[0] 中,而不是保存在 _position 这样的成员变量中。每个循环都有自己的 iter,所以两个遍历同一个团队的循环,即使一个嵌套在另一个里面,也不会互相干扰。如果使用成员变量,内层循环就会改变外层循环的位置。

列表的其他操作没有虚拟方法:len(team) 和 team[0] 都会报错。请编写普通方法,并使用与 Array 类似的名称,例如 size()、get_at(index) 和 has(member)。不要使用 in 检查成员资格:"Ada" in team 检查的是 Team 是否有名为 Ada 的属性或方法,因此结果是 false。

challenge icon

挑战

简单

在 bookshelf.gd 中编写 Bookshelf 类。它将书名保存在 _titles 中,最多可存放 _capacity 本书:

  • add(title) 会追加书名并返回 true;如果书架已满或已有该书名,则不会添加,并返回 false。
  • size() 返回书本数量,has(title) 则返回书架上是否有该书名。
  • get_at(index) 返回该索引处的书名;如果索引超出书架范围,则返回空字符串。
  • _iter_init()、_iter_next() 和 _iter_get() 让 for 循环按书名添加的顺序遍历它们。

提供的代码会读取容量、书名数量以及每行一个书名,并报告无法添加的书名。它会打印书本数量,用 for 循环遍历书架并列出书名,然后读取另一个书名,打印书架上是否有这本书,以及最后一本书。当容量为 3,书名依次为 Dune、Emma、Dune、Ulysses、Hamlet,查询的书名为 Emma 时,它会打印:

Could not add: Dune
Could not add: Hamlet
Books: 3
1. Dune
2. Emma
3. Ulysses
Has Emma: true
Last: Ulysses

你的代码写在 bookshelf.gd 中。main.gd 包含提供的输入/输出代码,不能编辑。

亲手试试

extends Node

func _ready():
	# 提供的输入/输出代码:保持原样。
	var shelf := Bookshelf.new(int(OS.read_string_from_stdin()))
	var count := int(OS.read_string_from_stdin())
	for i in count:
		var title := OS.read_string_from_stdin()
		if not shelf.add(title):
			print("Could not add: " + title)
	print("Books: %d" % shelf.size())
	var number := 1
	for title in shelf:
		print("%d. %s" % [number, title])
		number += 1
	var query := OS.read_string_from_stdin()
	print("Has %s: %s" % [query, shelf.has(query)])
	print("Last: " + shelf.get_at(shelf.size() - 1))
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: GDScript 在线编译器