生成器是自PHP5.5.0起引入的新特性,允许以更容易的方法来实现对象迭代。生成器允许在foreach代码块中写代码来迭代一组数据,而无需在内存中创建一个数组,减少内存开销
简单的示例
使用传统的range($start, $end [, $step = 1])
,可能会造成较大的内存消耗。如range(0, 1000000)
,消耗的内存会超过100MB。而使用生成器,则能够解决这个问题:
function xrange($start, $limit, $step = 1) {
if($start < $limit) {
if($step <= 0) {
throw new LogicException("Step must be +ve");
}
for($i=$start; $i<=$limit; $i+=$step) {
yield $i;
}
} else {
if($step >= 0) {
throw new LogicException("Step must be -ve");
}
for($i=$start; $i>=$limit; $i+=$step) {
yield $i;
}
}
}
此种情况下,运行以下代码效果一致:
foreach(range(1, 5, 2) as $val) {
echo $val." ";
}
// 输出:1 3 5
foreach(xrange(1, 5, 2) as $val) {
echo $val." ";
}
// 输出:1 3 5
生成器语法
1、生成器是通过yield
关键字来返回值的,普通函数只能返回一个值,而使用yield
则可以返回多个值。yield会返回一个可被遍历的对象,在每次需要的时候可以调用生成器函数,然后在产生一个值之后,状态将被保存下来,如此一来可以实现 调用一次就迭代一次的效果
2、如果要在一个表达式上下文中使用yield
,则需要用圆括号包围起来,如$data = (yield $value)
,而不能使用$data = yield $value
的形式
3、生成器可以同时生成键和值,使用yield $key => $value
的形式,如:
$stuList = <<<'EOF'
14001 Tom
14002 Jack
14003 Michal
EOF;
function parseList($list) {
$list = explode("\n", $list);
foreach($list as $each) {
$fields = explode(" ", $each);
yield $fields[0] => $fields[1];
}
}
echo "Students are:\n";
foreach(parseList($stuList) as $key => $val) {
echo '['.$key.'] '.$val.PHP_EOL;
}
/*
输出:
[14001] Tom
[14002] Jack
[14003] Michal
*/
4、如果没有yield
值的话,会生成null
值,即yield;
的情况下,迭代返回的是null
5、可以使用引用来生成值,会导致yield的值也随之变化,如:
function &gen() {
$value = 3;
while($value > 0) yield $value;
}
foreach(gen() as &$num) {
echo ($num--)."\n";
}
6、在PHP中,可以使用yield from
来从另一个生成器、Traversable对象、数组导入,来生成值,如:
function gen() {
yield 1;
yield from twoToThree();
yield from [4, 5];
}
function twoToThree() {
yield 2;
yield 3;
}
foreach(gen() as $val) {
echo $val." ";
}
// 将输出:1 2 3 4 5
生成器和Iterator接口对象对比
- 使用生成器和遵循Iterator接口的对象,都可以实现迭代的效果。但是,相比Iterator对象,使用生成器最主要的优点就是简单、更少的代码就能实现,而且宽可读性更强。
- 不过,生成器是只能前进的迭代器,一旦开始迭代后,就不能从头再来。而Iterator对象则能够使用
rewind()
方法,将迭代指针指向头部从而可以从头再开始迭代