生成器 生成器总览 生成器提供了一种更容易的方法来实现简单的对象迭代,相比较定义类实现 Iterator 接口的方式,性能开销和复杂性大大降低。 生成器提供了方便的方式来向 &foreach; 循环提供数据,而无需提前在内存中构建数组,这可能会导致程序超出内存限制或需要相当长的处理时间来生成。相反,可以使用生成器函数,与普通的 function 相同,不同之处在于生成器不是 return 一次,而是可以根据需要多次 &yield; 以提供要迭代的值。与迭代器一样,无法进行随机数据访问。 一个简单的例子就是使用生成器来重新实现 range 函数。 标准的 range 函数需要在内存中生成一个数组包含每一个在它范围内的值,然后返回该数组, 结果就是会产生多个很大的数组。 比如,调用 range(0, 1000000) 将导致内存占用超过 100 MB。 做为一种替代方法, 我们可以实现一个 xrange() 生成器, 只需要足够的内存来创建 Iterator 对象并在内部跟踪生成器的当前状态,这样只需要不到1K字节的内存。 将 <function>range</function> 实现为生成器 = 0) { throw new LogicException('Step must be negative'); } for ($i = $start; $i >= $limit; $i += $step) { yield $i; } } } /* * 注意下面range()和xrange()输出的结果是一样的。 */ echo 'Single digit odd numbers from range(): '; foreach (range(1, 9, 2) as $number) { echo "$number "; } echo "\n"; echo 'Single digit odd numbers from xrange(): '; foreach (xrange(1, 9, 2) as $number) { echo "$number "; } ?> ]]> &example.outputs; <classname>Generator</classname> 对象 调用生成器函数时会返回一个内部的 Generator 类的对象。 该对象实现了 Iterator 接口,基本上和仅向前的迭代器一样, 它提供的方法可以操控生成器的状态,包括发送值、返回值。 生成器语法 生成器函数看起来像普通函数——不同的是普通函数返回一个值,而生成器可以 &yield; 生成多个想要的值。 任何包含 &yield; 的函数都是一个生成器函数。 当一个生成器被调用的时候,它返回一个可以被遍历的对象.当你遍历这个对象的时候(例如通过一个&foreach;循环),PHP 将会在每次需要值的时候调用对象的遍历方法,并在产生一个值之后保存生成器的状态,这样它就可以在需要产生下一个值的时候恢复调用状态。 一旦不再需要产生更多的值,生成器可以简单返回,而调用生成器的代码还可以继续执行,就像一个数组已经被遍历完了。 生成器能够返回多个值,通过 Generator::getReturn 可以获取到。 <command>yield</command> 关键字 生成器函数的核心是yield关键字。它最简单的调用形式看起来像一个return申明,不同之处在于普通return会返回值并终止函数的执行,而yield会返回一个值给循环调用此生成器的代码并且只是暂停执行生成器函数。 一个简单的生成值的例子 ]]> &example.outputs; 在内部会为生成的值配对连续的整型索引,就像一个非关联的数组。 指定键名来生成值 PHP的数组支持关联键值对数组,生成器也一样支持。所以除了生成简单的值,你也可以在生成值的时候指定键名。 如下所示,生成一个键值对与定义一个关联数组十分相似。 生成一个键值对 $fields; } } foreach (input_parser($input) as $id => $fields) { echo "$id:\n"; echo " $fields[0]\n"; echo " $fields[1]\n"; } ?> ]]> &example.outputs; 生成 null 值 Yield 可以在没有参数传入的情况下被调用来生成一个 &null; 值并配对一个自动的键名。 生成&null;s ]]> &example.outputs; NULL [1]=> NULL [2]=> NULL } ]]> 使用引用来生成值 生成函数可以像使用值一样来使用引用生成。这个和从函数返回一个引用一样:通过在函数名前面加一个引用符号。 使用引用来生成值 0) { yield $value; } } /* * 我们可以在循环中修改 $number 的值,而生成器是使用的引用值来生成,所以 gen_reference() 内部的 $value 值也会跟着变化。 */ foreach (gen_reference() as &$number) { echo (--$number).'... '; } ?> ]]> &example.outputs; 可用的 <command>yield from</command> 生成器委托 生成器委托允许使用 yield from 关键字从另外一个生成器、 Traversable 对象、array 通过生成值。 外部生成器将从内部生成器、object、array 中生成所有的值,直到它们不再有效, 之后将在外部生成器中继续执行。 如果生成器与 yield from 一起使用,那么 yield from 表达式将返回内部生成器返回的任何值。 存储到 array (例如使用 <function>iterator_to_array</function>) yield from 不能重置 key。它保留 Traversable 对象或者 array 返回的 key。因此,某些值可能会与其他的 yield 或者 yield from 共享公共的 key,因此,在插入数组时将会用这个 key 覆盖以前的值。 一个非常重要的常见情况是 iterator_to_array 默认返回带 key 的 array , 这可能会造成无法预料的结果。 iterator_to_array 还有第二个参数 preserve_keys ,可以设置为 &false; 来收集 Generator 返回的不带 key 的所有值。 使用 <function>iterator_to_array</function> 的 <command>yield from</command> ]]> &example.outputs; int(1) [1]=> int(4) [2]=> int(3) } ]]> <command>yield from</command> 的基本用法 ]]> &example.outputs; <command>yield from</command> 并返回多个值 getReturn(); ?> ]]> &example.outputs; 生成器与 <classname>Iterator</classname> 对象的比较 生成器最主要的优点是简洁。和实现一个 Iterator 类相较而言, 同样的功能,用生成器可以编写更少的代码,可读性也更强。 举例,下面的类和函数是相等的: fileHandle = fopen($fileName, 'r')) { throw new RuntimeException('Couldn\'t open file "' . $fileName . '"'); } } public function rewind() { fseek($this->fileHandle, 0); $this->line = fgets($this->fileHandle); $this->i = 0; } public function valid() { return false !== $this->line; } public function current() { return $this->line; } public function key() { return $this->i; } public function next() { if (false !== $this->line) { $this->line = fgets($this->fileHandle); $this->i++; } } public function __destruct() { fclose($this->fileHandle); } } ?> ]]> 不过,这也付出了灵活性的代价: 生成器是一个只能向前的迭代器,一旦开始遍历就无法后退。 意思也就是说,同样的生成器无法遍历多次:要么再次调用生成器函数,重新生成后再遍历。 &reftitle.seealso; 遍历对象