![PHP面试一战到底](https://wfqqreader-1252317822.image.myqcloud.com/cover/891/44509891/b_44509891.jpg)
4.4 生成器(Generator)与yield
前面在1.1.8小节讲述过迭代器。一个迭代器是这样一个容器,它实现了Traversable接口,从而能够遍历容器中的内部元素。PHP中最常见的迭代器是数组,能使用foreach来遍历所有的元素。
与迭代器密切相关的概念是生成器(Generator)和yield。
4.4.1 生成器
为了解释生成器,我们先看一个现实中的例子。
假设某工厂有5000名工人,为解决就餐问题开设了食堂,每餐每人有1碗米饭1盘菜。工人打饭时有两个方案:
方案1:食堂工作人员把5000份套餐全部打包好,放在桌子上,依次发给工人。
方案2:工人排队,食堂工作人员依次给工人打饭。
现实生活中第1种方案并不常见,因为事先把所有套餐全部打包好,需要占用餐厅5000个位置来放置这些饭盒,很浪费空间;而采用第2种方案,米饭和菜只需分别放在一个大桶里,随用随取即可。
将这个例子放到程序里可以有如下表示:
1.数组里有5000个元素
![](https://epubservercos.yuewen.com/9146EB/23721566801954006/epubprivate/OEBPS/Images/Figure-P73_39725.jpg?sign=1738880870-WPBQU9VsRzeTltN2dzv0CiooIPPtf1at-0-7764b1c14e427c0bfc664e63a273241a)
2.采用自定义的生成函数生成
(源码文件:ch04/generator.php)
![](https://epubservercos.yuewen.com/9146EB/23721566801954006/epubprivate/OEBPS/Images/Figure-P73_39726.jpg?sign=1738880870-R5gFZzhs3yBFxjgjSimM8gGPJRoZSehG-0-d3f34097b4d1cc7fd2f3596ccba166c3)
采用memory_get_usage方法打印出两种方案所需的内存使用量:
方案1:968912
方案2:226504
可以看到方案2的内存使用量仅是方案1的1/4。方案2也有其他优点:实现简单,1个人就可以打饭;方案1装满5000份套餐所需时间太长,后面装好前面就凉了,方案2随用随取。
生成器是用来生成迭代器的函数,其优点有以下三个:
● 实现简单。
● 避免分配大块内存,防止程序超过内存限制。
● 避免生成迭代器的执行时间过长。
4.4.2 yield
yield应用于生成器函数里,类似于return,但略有不同:
● yield可以有多个。
● yield会记住上次返回的值,下次调用时会返回下一个yield。
例如以下示例中,有3个yield,每次遍历时会依次返回1,2,3的值,从而实现遍历。
(源码文件:ch04/yield_demo)
![](https://epubservercos.yuewen.com/9146EB/23721566801954006/epubprivate/OEBPS/Images/Figure-P74_39737.jpg?sign=1738880870-NiloMy8UXCcXMt3PuLt87myo07IXOCo4-0-3160d822e8330c0d7e11abefae519414)
4.4.3 生成器的设计
Generator可以生成一个可迭代的容器,显然这个容器的容量是有限的。那么设计生成器时,就引出一个问题:是由Generator控制数目,还是在循环中控制呢?这里分享一个规则:
● 规则1:当数据有一定规则可循时,可以由Generator控制数目。例如生成奇数,计算阶乘等。
● 规则2:当数据无规则可循或随机时,一般在循环中控制。
为了便于理解,我们实现两个Generator。
1.生成奇数
编写一个函数,当输入n时,输出n个奇数。
程序代码如下:(源码文件:ch04/generator_odd.php)
![](https://epubservercos.yuewen.com/9146EB/23721566801954006/epubprivate/OEBPS/Images/Figure-P74_39742.jpg?sign=1738880870-yNTwlg4767XGNdV7WM8dxkTuny6T3nHY-0-c3fc15c386395e7e4b5404555fbad6ba)
2.生成uuid
uuid是通用唯一识别码(Universally Unique Identifier),理论上每个uuid都是全局唯一的,这在生成不重复的标识符时非常有用,如订单号、物流号。按照概率论计算,一个人每年被陨石击中的概率大概是170亿分之一,而两个uuid重复的概率比这还小。
程序示例如下:(源码文件:ch04/generator_uuid.php)
![](https://epubservercos.yuewen.com/9146EB/23721566801954006/epubprivate/OEBPS/Images/Figure-P75_39744.jpg?sign=1738880870-95oyCzr5Jx5ytAZHuE6dz3ZaAAyReYI9-0-67ec36bcb9a8bb5637f51b9931c7b7d2)
这个例子中,采用外部变量$num来控制遍历的数目,一旦达到数量限制,遍历过程会被break,从而控制了循环的流程。
4.4.4 面试题:用yield实现斐波那契数列
题目描述:输入n,返回斐波那契数列的前n个数,要求用yield实现。
解答:斐波那契数列的每一项都是前两个数之和,例如:
0,1,1,2,3,5,8,13,...
实现代码如下:(源码文件:ch04/yield_fibonacci.php)
![](https://epubservercos.yuewen.com/9146EB/23721566801954006/epubprivate/OEBPS/Images/Figure-P76_39745.jpg?sign=1738880870-rcJXsjAFXmXd5lQmZwwePSZLsuCxDajo-0-a0f16174efeab99a26d1bb4a7bea37f1)
引申思考:为什么不用递归实现斐波那契数列呢?
递归实现斐波那契数列的确比较简单,但存在重复计算的问题;而yield实现(这种方法叫动态规划)时,能够暂存子问题的结果,因此效率更高些。