最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • PHP类的秘密(九) 迭代器iterator与IteratorAggregate

    正文概述 转载于:掘金(传达室老大爷)   2021-06-30   306

    这是我参与更文挑战的第11天,活动详情查看: 更文挑战

    迭代器iterator与IteratorAggregate

    这章的内容本来是想和上一篇<对象的秘密>一起讲, 不过碍于篇幅太长, 还是单独开一篇专门总结一下.

    foreach

    foreach是遍历对象的工具, 它可以自己单独使用, 也可以配合迭代器, 生成器一同使用.

    下面是foreach单独使用时遍历对象的例子. 非常简单, 就和遍历数组是一样的, 可以用在类内, 也可以用在类外:

    class MyClass
    {
        public $var1 = 'value 1';
        protected $protected = 'protected var';
        private   $private   = 'private var';
    
        function iterateVisible() 
        {
           foreach($this as $key => $value) {  //类内, foreach定义在成员方法;
           print "$key => $value\n";
           }
        }
    }
    
    $class = new MyClass();
    foreach($class as $key => $value) {    //作为类外函数读取对象
    print "$key => $value\n";
    }                                  //var1 => value 1, 仅输出public属性
    $class->iterateVisible()       //var1 => value 1, protected => protected var, private => private var 输出所有对象属性;
    

    通过上例, foreach如果定义在类内, 则可以输出所有属性, 如果在类外作为函数调用, 则只可以输出public属性.

    如果对象属性中有数组型, 也可以将上例优化为foreach($this->array as $key=>$value)进行遍历.

    单独使用foreach有其局限, 就是无法让对象自行决定遍历的起点, 以及每次遍历时那些值可用。想让foreach遍历得更加灵活, 就需要迭代器上场了.

    迭代器iterator{}

    迭代器iterator{}的作用是, 当执行foreach遍历对象时, 被自动调用, 规定了遍历的方法.

    Iterator是一个php预定义的接口类, 可以直接使用, 这是它内部的结构:

    Iterator extends Traversable 
    {
    abstract public current(): mixed — 返回当前元素
    abstract public key(): scalar — 返回当前元素的键
    abstract public next(): void — 向前移动到下一个元素
    abstract public rewind(): void — 返回到迭代器的第一个元素
    abstract public valid(): bool — 检查当前位置是否有效
    }
    

    可以看到iterator{}是Traversable{}的子类, Traversable{}是检测一个类是否可以使用 foreach 进行遍历的接口。但无法被单独实现, 一般的用法如下:

    if( !is_array( $items ) && !$items instanceof Traversable )
        //Throw exception here
    

    Traversable{}必须由 IteratorAggregate 或 Iterator 接口实现。而使用iterator{}需要定义类实现接口, 如class MyIterator implements Iterator{}

    iterator迭代器内部包含5种抽象方法, 都需要在其他类中实现. 而且每种方法必须是public的, 否则无法由foreach, while等函数在类外进行自动调用.

    我们来实际看一个例子, 看看foreach是如何调用这些成员方法实现遍历的:

    class myIterator implements Iterator {
        private $position = 0;
        private $array = array
        (
            "firstelement",
            "secondelement",
            "lastelement",
        );  
    
    public function __construct() {  //构造函数初始化属性
        $this->position = 1;
    }
    
    function rewind() {              //返回到迭代器的第一个元素, 决定迭代起点
        var_dump(__METHOD__);
        $this->position = 1;
    }
    
    function current() {                //返回当前属性值
        var_dump(__METHOD__);
        return $this->array[$this->position];
    }
    
    function key() {                   //返回当前的$key
        var_dump(__METHOD__);
        return $this->position;
    }
    
    function next() {                   //确定下一次迭代时的位置
        var_dump(__METHOD__);
        ++$this->position;
    }
    
    function valid() {               //验证是否有值
        var_dump(__METHOD__);
        return array_key_exists($this->array, $this->position);
    }
    }
    
    $it = new myIterator;
    
    foreach($it as $key => $value) {     //外部函数调用内部接口
    var_dump($key, $value);
    echo "\n";
    }
    //output:
    string(18) "miIterator::rewind"
    string(17) "miIterator::valid"
    string(19) "miIterator::current"
    string(15) "miIterator::key"
    int(1)
    string(13) "secondelement"
    
    string(16) "miIterator::next"
    string(17) "miIterator::valid"
    string(19) "miIterator::current"
    string(15) "miIterator::key"
    int(2)
    string(11) "lastelement"
    
    string(16) "miIterator::next"
    string(17) "miIterator::valid"
    

    上例中foreach函数会在遍历时输出了对象$it的私有属性$array, 并且不是全部输出, 而是从第[1]个元素开始输出, 我们一条条分析以下它是如何做到的:

    string(18) "myIterator::rewind"      //迭代开始前调用rewind方法, 返回到迭代器的第一个元素, 同时执行$this->position = 1;
    
    string(17) "myIterator::valid"      //验证当前元素$this->array[1]是否有效, 有效则继续执行;
    
    string(19) "myIterator::current"    // 返回当前元素对应的值$this->array[0]='firstelement'
    
    string(15) "myIterator::key"       // 返回当前元素的键$this->position = 1
    
    int(1)                             //var_dump($key)
    
    string(12) "secondelement"         //var_dump($value)
    
    string(16) "myIterator::next"     //向前移动到下一个元素++$this->position=2
    
    string(17) "myIterator::valid"    //验证当前元素$this->array[2]是否有效, 有效则继续执行;
    
    string(19) "myIterator::current"  //重复之前的步骤
    
    string(15) "myIterator::key"      //重复之前的步骤
    
    int(2)                             //重复之前的步骤
    
    string(11) "lastelement"           //重复之前的步骤   
    
    string(16) "myIterator::next"      //向前移动到下一个元素++$this->position=3
    
    string(17) "myIterator::valid"      //验证当前元素$this->array[3]是否有效, 无效则退出遍历
    

    通过上面的分析, 我们可以得到迭代器的工作原理:

    1. 在第一次迭代前, 先运行iterator::rewind()方法, 并且只运行一次, 返回迭代器第一个元素, 但这个方法是没有返回值的;

    2. 并且紧接着会验证第一个元素是否有效, 验证方法可以自定义, 如果返回值为true, 则继续执行, 如果返回值为false则迭代终止.

    3. foreach继续调用Iterator::current()和Iterator::key(), 返回指定元素的键和值.

    4. foreach的方法体. 上例中的var_dump($key, $value);

    5. 在执行每次迭代后, 调用Iterator::next()决定下一次的元素并重复第2步.

    通过迭代器iterator的5个方法, 我们实现了对遍历的灵活控制!

    此外, iterator迭代器还可以作为容器, 去遍历其他类的对象, 这里就用到了聚合式迭代器IteratorAggregate{}接口

    聚合式迭代器IteratorAggregate{}接口

    所谓聚合式, 就是去实现其他迭代器功能的接口. 相当于在其他迭代器上套了个壳. 它只有一个方法:

    abstract public IteratorAggregate::getIterator() 获取一个外部迭代器

    比如我们可以尝试将iterator{}和IteratorAggregate{}进行结合:

    class MainIterator implements Iterator
    {
        private $var = array();
        public function __construct($array)    //构造函数, 初始化对象数组
        {
            if (is_array($array)) { 
            $this->var = $array;
            }
        }
    
        public function rewind() {   
            echo "rewinding\n";
            reset($this->var);    //将数组的内部指针指向第一个单元
        }
    
        public function current() {
            $var = current($this->var);    // 返回数组中的当前值
            echo "current: $var\n";
            return $var;
        }
    
        public function key() {
            $var = key($this->var);       //返回数组中内部指针指向的当前单元的键名
            echo "key: $var\n";
            return $var;
        }
    
        public function next() {
            $var = next($this->var);     //返回数组内部指针指向的下一个单元的值
            echo "next: $var\n";
            return $var;
        }
    
        public function valid() {
        return !is_null(key($this->var); //判断当前单元的键是否为空
        }
    }
    

    我们通过上面的类定义, 实现了一个迭代器MainIterator{}; 现在这个构造器就可以被IteratorAggregate{}使用:

    class MyCollection implements IteratorAggregate
    {
        private $items = array();
        private $count = 0;
        
        public function getIterator() {       //获取一个外部迭代器
            var_dump(__METHOD__);
            return new MainIterator($this->items);  //使用MainIterator接口创建实例
        }
    
        public function add($value) {
            $this->items[$this->count++] = $value;
        }
    }
    $coll = new MyCollection();
    $coll->add('value 1');
    $coll->add('value 2');
    $coll->add('value 3');
    
    foreach ($coll as $key => $val) {
        echo "key/value: [$key -> $val]\n\n";
    }
    //output:
    Collection::getIterator
    rewinding
    current: value 1
    key: 0
    key/value: [0 -> value 1]
    
    next: value 2
    current: value 2
    key: 1
    key/value: [1 -> value 2]
    
    next: value 3
    current: value 3
    key: 2
    key/value: [2 -> value 3]
    
    next: 
    

    上面这个例子, 最关键的是这句话:

     public function getIterator() {       
            return new MainIterator(\$this->items);}
            
    

    翻译一下: 实例化器MainInterator构造, 并将原对象$coll的属性$items传给$var.

    可以看到foreach ($coll as $key => $val)会自动调用getIterator()方法, 也就是调用了MainIterator构造器, 最终实现了遍历.

    当然, 聚合型迭代器可以与很多迭代器融合, 实现更高效的迭代, 在PHP SPL标准库中有很多已经预定义好的迭代器大概20多个. 比如ArrayIterator{}, 这个迭代器允许在遍历数组和对象时删除和更新值与键。

    class myData implements IteratorAggregate {
        private $property1 = ["Public property one",'test'];
    
        public function getIterator() {  //创建ArrayIterator迭代器的实例, 并传属性
        return new ArrayIterator($this->property1);
        }
    }
    
    $obj = new myData;
    foreach($obj as $key => $value) {
        var_dump($key, $value);
    }
    

    两者区别

    通过之前的介绍, 我们已经知道两者在功能上有一定区别, 除此之外, 在工作模式上也有一些区别:

    我们在上一节代码的基础上, 实现一个简单的递归迭代, 我们先看看mainIterator{}的处理方式:

    $list = new mainIterator([1, 2]);    //新建mainIterator对象
    foreach($list as $i){
        foreach($list as $i)
            echo $i;
            echo '\n';
    }
    //output:
    rewinding
    current: 1
    rewinding
    current: 1
    1
    next: 2
    current: 2
    2
    next: 
    next: 
    

    这个结果是有问题的, 在执行了第二层递归时, 数组内部指针已经到达末尾, valid()已经false, 整体强制退出了foreach, 并没有执行第二轮迭代;

    如果我们使用IteratorAggregate来调用iterator迭代器呢?

    $list1= new Collection();
    $list1->add(1);
    $list1->add(2);
    foreach ($list1 as $i){
        foreach ($list1 as $i)
            echo $i;
        };
    //output:
    string(23) "Collection::getIterator"
    rewinding    current: 1
    string(23) "Collection::getIterator"
    rewinding   current: 1 1
    next: 2    current: 2 2 
    next:    
    next: 2    current: 2
    string(23) "Collection::getIterator"
    rewinding     current: 1 1
    next: 2    current: 2  2
    next: 
    next: 
    

    可以看到, foreach通过IteratorAggregate接口成功完成了两次迭代. 差别在哪里呢?

    IteratorAggre方法在每次遇到foreach的时候, 都会重新调用getIterator方法, 也就是再整体重新跑一边迭代器iterator. 这样做的结果就是实现了嵌套, 有了递归的作用在里面. 可以满足递归迭代的需求.

    而一元的iterator迭代器, 却没有这种嵌套结构, 光标到尾部, 只能退出.

    **总结一下:

    通过iterator{}我们可以通过内部的5个方法, 设置各种各样的迭代器.

    iterator{}可以单独用来迭代对象和数组, 但IteratorAggregate{}需要调用不同的外部迭代器.

    从代码复杂度上, iterator要高于iteratorAggregator.

    执行效率上也是iteratorAggregator更高一些.

    iterator{}处理递归迭代时会有问题, 而iteratorAggregate会在每次foreach运行时被调用, 更安全.

    另外再补充一点, iterator构造器不仅可以被foreach调用, 还可以被while, for等循环语句都可以调用构造器的方法, 实现遍历, 相当于是手动做了个getiterator.


    感谢阅读, 如有不准确和错误的地方请指正, 我会及时修正, 拜谢!

    总结不易, 请勿私自转载, 否则别怪老大爷不客气

    欢迎热爱技术的小伙伴儿和我交流, 共同成长!

    参考资料:

    www.php.net PHP官方文档

    PHP interfaces IteratorAggregate vs Iterator?

    注意Iterator和IteratorAggregate之间的区别


    起源地下载网 » PHP类的秘密(九) 迭代器iterator与IteratorAggregate

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元