Menu
CoddyTech

生成器与迭代器

本课属于 Coddy PHP 学习旅程的“面向对象编程”单元。第 62 课,共 91 课。

生成器提供了一种简单的方法,无需实现完整的 Iterator 接口即可创建迭代器。生成器函数使用 yield 而不是 return 来逐个生成值,并在每个值之间暂停执行。

这是一个生成数字序列的基本生成器:

<?php
function countTo(int $max): Generator {
    for ($i = 1; $i <= $max; $i++) {
        yield $i;
    }
}

foreach (countTo(3) as $number) {
    echo "$number\n";
}

输出:

1
2
3

主要优势是节省内存。生成器不会在内存中创建整个数组,而是按需生成值。处理大型数据集时,这一点至关重要。处理 100 万条记录的生成器,使用的内存与处理 10 条记录的生成器相同。

生成器也可以生成键值对:

<?php
function userGenerator(): Generator {
    yield 'admin' => 'Alice';
    yield 'editor' => 'Bob';
}

foreach (userGenerator() as $role => $name) {
    echo "$role: $name\n";
}

输出:

admin: Alice
editor: Bob

要让你自己的类可以使用 foreach 进行迭代,请实现 Iterator 接口。不过,在大多数情况下,生成器能提供更简洁的解决方案,所需的样板代码也更少。当一个类需要提供一组项目,又不想一次性将所有内容加载到内存中时,生成器在面向对象编程中尤其有用。

challenge icon

挑战

简单

让我们构建一个产品库存系统,使用生成器高效地遍历大型集合,而无需一次性将所有内容加载到内存中。你将创建一个存储产品的类,并提供生成器方法,以不同方式遍历这些产品。

你将把代码分为 3 个文件:

  • Product.php:创建一个表示库存中某个商品的 Product 类。使用构造函数属性提升来定义公共 $sku(字符串)、公共 $name(字符串)和公共 $price(浮点数)。
  • Inventory.php:创建一个管理产品集合并提供生成器方法来遍历产品的 Inventory 类。引入 Product 文件。这个类应该:
    • 具有一个用于存储产品的私有数组属性
    • 具有一个 addProduct(Product $product) 方法,用于将产品添加到集合中
    • 具有一个 allProducts(): Generator 生成器方法,每次生成一个产品
    • 具有一个 expensiveProducts(float $minPrice): Generator 生成器方法,只生成价格大于或等于给定最低价格的产品,并生成键值对,其中键是 SKU,值是产品
  • main.php:引入 Inventory 文件。你会收到一个输入:最低价格阈值。

    创建一个 Inventory,并添加以下 3 个产品:

    • SKU:"A001",名称:"Laptop",价格:999.99
    • SKU:"A002",名称:"Mouse",价格:29.99
    • SKU:"A003",名称:"Monitor",价格:349.99

    首先,遍历 allProducts(),并将每个产品的名称单独打印在一行上。

    然后打印一个空行,接着使用输入的阈值遍历 expensiveProducts()。对于每个价格较高的产品,在单独一行上打印 "[sku]: [name] - $[price]"。价格格式必须恰好保留 2 位小数。

这个挑战展示了生成器如何让你延迟遍历集合:只有在需要时才会生成每个产品,因此这种方法在处理大型库存时能节省内存。expensiveProducts() 中的键值生成展示了生成器如何提供结构化的遍历数据。

亲手试试

<?php

require_once 'Inventory.php';

// 读取输入
$minPrice = floatval(trim(fgets(STDIN)));

// TODO:创建一个 Inventory 实例

// TODO:添加 3 个商品:
// SKU:"A001",名称:"Laptop",价格:999.99
// SKU:"A002",名称:"Mouse",价格:29.99
// SKU:"A003",名称:"Monitor",价格:349.99

// TODO:遍历 allProducts(),打印每个商品的名称

// TODO:打印一个空行

// TODO:使用 $minPrice 遍历 expensiveProducts()
// 对每个高价商品,打印 "[sku]: [name] - $[price]"
// 将价格格式化为恰好 2 位小数

?>
quiz icon自我测试

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

面向对象编程中的所有课

自己练习: PHP 在线编译器