PHP 生成器Generator理解

转载整理自: 寄凡风雪之隅、PHP手册

生成器(Generator)

  • 可解决的问题

        引用自官网:生成器提供了一种更容易的方法来实现简单的对象迭代,相比较定义类实现Iterator接口的方式,性能开销和复杂性大大降低。生成器允许你在foreach代码块中写代码来迭代一组数据而不需要在内存中创建一个数组,那会使你的内存达到上限(划重点),或者会占据可观的处理时间。相反,你可以写一个生成器函数,就像一个普通的自定义函数一样,和普通函数只返回一次不同的是, 生成器可以根据需要 yield 多次,以便生成需要迭代的值。

  • Generate实现了Iterator的接口
<?php
//生成器
Generator implements Iterator {
    //返回当前产生的值
    public mixed current ( void )
    //返回当前产生的键
    public mixed key ( void )
    //生成器继续执行
    public void next ( void )
    //重置迭代器,如果迭代已经开始了,这里会抛出一个异常。
    public void rewind ( void )
    //向生成器中传入一个值,当前yield接收值,然后继续执行下一个yield
    public mixed send ( mixed $value )
    //向生成器中抛入一个异常
    public void throw ( Exception $exception )
    //检查迭代器是否被关闭,已被关闭返回 FALSE,否则返回 TRUE
    public bool valid ( void )
    //序列化回调
    public void __wakeup ( void )
    //返回generator函数的返回值,PHP version 7+
    public mixed getReturn ( void )
}
?>
  • 关键字yield
        生成器函数的核心是yield关键字。它最简单的调用形式看起来像一个return申明,不同之处在于普通return会返回值并终止函数的执行,而yield会返回一个值给循环调用此生成器的代码并且只是暂停执行生成器函数. 一个生成器不可以返回值:这样做会产生一个编译错误。然而return空是一个有效的语法并且它将会终止生成器继续执行。


        yield只能在函数中使用,否则会报PHP Fatal error:The "yield" expression can only be used inside a function,凡是使用了yield关键字的函数都会返回一个Generator对象。每次代码执行到yield语句都会中止执行,返回yield语句中表达式的值给Generator对象,继续迭代Generator对象时,yield后面的代码会接着执行,直到所有yield语句全部执行完毕或者有return语句,这个renturn语句只能返回null,即return;,否则会编译错误。

  • 从实例出发理解执行过程和可使用函数

        1 实例一

<?php
function xrang($start, $end, $step=1){
    for($i=$start; $i<=$end; $i += $step) {
        yield $i;  //yield关键字定义了中断点
    }   
}


//foreach (xrang(1, 10000) as $num) {
//  echo $num."\n"; 
//}


$rang = xrang(1,2);
var_dump($rang).PHP_EOL; //输出: object(Generator)#1 (0) {}
var_dump($rang instanceof Iterator).PHP_EOL; //输出: bool(true)


$key = $rang->key(); 
var_dump("key: ".$key).PHP_EOL; //输出: string(6) "key: 0"
$valid = $rang->valid();
var_dump("valid: ".$valid).PHP_EOL; //输出: string(8) "valid: 1"
$current = $rang->current();
var_dump("current: ".$current).PHP_EOL; //输出: string(10) "current: 1"


$rang->next();


$key = $rang->key(); 
var_dump("key: ".$key).PHP_EOL; //输出: string(6) "key: 1"
$valid = $rang->valid();
var_dump("valid: ".$valid).PHP_EOL; //输出: string(8) "valid: 1"
$current = $rang->current();
var_dump("current: ".$current).PHP_EOL; //输出: string(10) "current: 2"


$rang->next();


$key = $rang->key();
var_dump("key: ".$key).PHP_EOL; //输出: string(5) "key: "
$valid = $rang->valid();
var_dump("valid: ".$valid).PHP_EOL; //输出: string(7) "valid: "


//$rang->rewind(); //重置,目前看到的所有文档中,rewind()仅在第一次调用Generator的时候隐式执行。生成器开始迭代后调用会抛出Fatal error。
?>
        2 实例二
<?php
function gen(){
    echo "1111\n";
    $ret = (yield 'yield1');
    var_dump($ret);
    echo "2222\n";
    $ret = (yield 'yield2');
    var_dump($ret);
    //return;
}
$gen = gen();
var_dump($gen->current()).PHP_EOL;
$a = $gen->send('ret1');
echo "66666\n";
var_dump($a).PHP_EOL;
echo "77777\n";
var_dump($gen->valid()).PHP_EOL;
$b = $gen->send('ret2');
var_dump($b).PHP_EOL;
var_dump($gen->valid()).PHP_EOL;


//1111
//string(6) "yield1"
//string(4) "ret1"
//2222
//66666
//string(6) "yield2"
//77777
//bool(true)
//string(4) "ret2"
//NULL
//bool(false)
?>
    2.1 执行过程为:

        1.首先调用gen(),进入函数输出1111,执行到第一个yield关键字所在的位置中断(此时yield表达式的值为定义的"yield1",使用current()获取当前表达式的值即得到string(6) "yield1")
        2.调用send()方法向生成器中传入值"ret1"(传入生成器的值.这个值将会被作为生成器当前所在的 yield 的返回值),此时生成器从当前所在的yield表达式开始迭代,程序继续往下执行   
        3.遇到var_dump输出当前表达式的值"ret1",继续执行输出2222
        4.继续执行,程序来到第二个yield中断点,此时表达式的值为定义值"yield2",因为调用的是send()方法,该方法返回当前所在的yield的值(current()方法值)。(查看send方法的官方文档)
        5.$a获取到send方法的返回值即"yield2",继续执行输出"66666", $a, "77777"
        6.输出当前生成器是否可用
        7.继续执行,向生成器中传入值"ret2",生成器开始继续迭代。此时生成器位于第二个yield表达式,该表达式接受"ret2"作为返回值赋予变量$ret,打印得到string(4) "ret2"。
        8.打印之后,$b == NULL,为NULL的原因因为未彻底理解清楚(疑问之处在于此时的send()方法到底有没有返回NULL),猜测可能有如下两个原因:
            8.1 一者可能是因为生成器之后没有中断点,也没有返回值(返回值不被允许,或者说仅允许返回return; return;用于终止生成器的执行),$gen->send()方法根本就没有返回任何东西,导致$b == NULL
           8.2 二者可能是$gen->send('ret2')传入值后,生成器迭代完本次的yield,隐式调用了next()和current(),又因为next()下面没有yield中断点使得current()返回NULL,导致send()返回值为NULL
             8.3 根据上下文,二的可能性更大


    2.2 关于send()方法
        send()向生成器中传入一个值,并且当做 yield 表达式的结果,然后继续执行生成器。如果当这个方法被调用时,生成器不在 yield 表达式,那么在传入值之前,它会先运行到第一个 yield 表达式。

猜你喜欢

转载自blog.csdn.net/caopingtao/article/details/79963103