Php5.5新特性 Generators詳解 原

PHP5.5.0版本中,新增了生成器*(Generators)特性,用於簡化實現迭代器接口(Iterator)*創建簡單的迭代器的複雜性。

通過生成器,我們可以輕鬆的使用foreach迭代一系列的數據,而不需要事先在內存中構建要被迭代的對象,大大減少了內存開銷。

當生成器函數被調用的時候,它會返回一個可迭代的對象,當對該對象進行迭代的時候,PHP將會在需要的時候調用生成器函數,並且在生成器使用新增的關鍵字yield產生一個新的值的時候,保存迭代器內部的狀態。迭代器沒有新的值需要產生的時候,生成器函數就可以直接退出,外部函數繼續執行。

注意,在生成器函數中,不能使用return語句返回值,使用return返回值的話會產生編譯器錯誤。但是,使用空的return是可以的,它會使迭代器終止。

生成器函數與普通函數一樣的,唯一的區別函數內使用了yield關鍵字。yield語句可以說是生成器函數的核心,簡單來說,yield就像return語句一樣,區別是return語句返回後函數就結束了,而使用yield返回後,只是暫停了函數的執行,轉到外部函數繼續執行,下次調用生成器函數的時候,繼續執行生成器函數內部的代碼。

####一個簡單的例子 - 生成器版本的range函數

一個簡單的例子是使用foreach迭代函數range的返回值,如果調用的是range(0, 1000000)的話,將會消耗超過100M的內存。而使用生成器的話,可能只需要消耗1KB內存都不到。

<?php
function xrange($start, $end) {
	if ($start > $end) {
    	throw new RuntimeException("起始值不能大於截止值");
    }
    for ($i = $start; $i <= $end; $i += 1) {
    	// 使用yield關鍵字,每次到這裏函數都會返回$i的值,並且控制權交給外部函數繼續執行
        yield $i;
    }
}

foreach (xrange(1, 9) as $number) {
    echo "$number ";
}

上面的例子輸出如下:

generator-output

上述例子中,我們創建了一個名爲xrange的函數,函數中使用yield不斷產生返回值,而調用xrange(1, 9)將會創建一個生成器對象。我們可以修改foreach這一行打印出xrange對象看看

...
$xrange_res = xrange(1, 9);
var_dump($xrange_res);
foreach( $xrange_res as $number){
...

輸出

generator-vardump

可以看出,執行xrange(1, 9)的時候確實是返回了一個Generator對象。

####使用Generator對象的send方法

在上面的例子中,我們使用yield語句的時候都是作爲單獨的一行語句執行的,也就是yield語句產生結果給外部,那麼在迭代過程中有沒有辦法從生成器函數外部獲取值呢?

辦法總是有的,因爲調用生成器函數後返回的是一個Generator對象,因此我們可以通過調用該對象的send方法從外部給生成器函數傳遞一個值,在調用send方法之後,yield會收到send函數發送的值。

<?php
function gen() {
    $ret = (yield 'yield1');
    var_dump("-->" . $ret);

    $ret = (yield 'yield2');
    var_dump("-->" . $ret);
}

$gen = gen();

var_dump($gen->current());
var_dump($gen->send('ret1'));
var_dump($gen->send('ret2'));

輸出:

generator-gen

這裏我們首先創建了名爲gen的生成器對象,然後打印$gen->current()方法的返回值,該返回值就是迭代器第一次迭代時產生的當前值,因此輸出了yield1

接下來我們調用了$gen->send('ret')方法,這時,生成器內第一個yield語句返回該方法傳遞的值ret1,因此輸出了$ret的值爲ret1

接着由於生成器內部執行到了第三條語句$ret = (yield 'yield2'),因此外部的第二個var_dump輸出了yield2。最後調用$gen->send('ret2')與第一次類似,不過這次生成器內部調用yield之後已經沒有yield了,因此返回的是NULL

注意,這裏的$ret = (yield 'yield2')語句中,使用括號包含了yield 'yield2'語句,這裏是必須的,如果在表達式上下文中使用yield,必須將yield放在括號內,否則會報錯。

####返回關聯數組

前面的例子中,我們使用yield關鍵字返回的總是單個值,實際上PHP也對返回關聯數組提供了支持,基本語法:

yield key => val

使用該語法格式可以在foreach的時候,返回與遍歷管理數組相同的結果。

<?php
function gen2() {
    $array = [
        'username' => 'mylxsw',
        'site'     => 'http://aicode.cc'
    ];

    foreach ($array as $key => $val) {
        yield $key => $val;
    }
}

foreach(gen2() as $key => $val) {
    var_dump($key . '   :   ' . $val);
}

輸出:

gen2-output

####使用引用

我們還可以讓生成器以引用的方式返回數據,這樣就可以在生成器外部直接修改生成器內部數據的值。

<?php
function &gen_reference() {
    $value = 3;

    while ($value > 0) {
        yield $value;
    }
}

foreach (gen_reference() as &$number) {
    echo (--$number).'... ';
}

上述例子中,需要注意的是,生成器函數的定義和遍歷的時候使用了&$number

最後,生成器與自定義的迭代器對象是不完全相同的,生成器一旦開始迭代,就不能再rewind了,只能一直向前迭代,直到迭代完成。如果希望多次迭代一個生成器對象的話,可以多次調用生成器函數創建新的生成器對象或者是使用clone關鍵字。


參考:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章