迭代器有时又称光标(cursor)是程式设计的软件设计模式,可在容器物件(container,例如list或vector)上遍访的接口,设计人员无需关心容器物件的内容,现在呢,各种语言实作Iterator的方式皆不尽同,有些面向对象语言像Java, C#, Python, Delphi都已将Iterator的特性内建语言当中,完美的跟语言整合,我们称之隐式迭代器(implicit iterator),但像是C++语言本身就没有Iterator的特色,但STL仍利用template实作了功能强大的iterator。

但是,PHP5开始支持了接口, 并且内置了Iterator接口, 所以如果你定义了一个类,并实现了Iterator接口,那么你的这个类对象就是ZEND_ITER_OBJECT,否则就是ZEND_ITER_PLAIN_OBJECT。对于ZEND_ITER_PLAIN_OBJECT的类,foreach会通过HASH_OF获取该对象的默认属性数组,然后对该数组进行foreach,而对于ZEND_ITER_OBJECT的类对象,则会通过调用对象实现的Iterator接口相关函数来进行foreach。

咱们什么也别说,先来看下迭代器的定义,那就是提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部显示。它可帮助构造特定的对象,那些对象能够提供单一标准接口循环或迭代任何类型的可计数数据。来看下迭代器模式的结构图:

咋样,反正我现在是一头雾水。。。

再来看下迭代器需要用到的内部方法:

  • Iterator::current — Return the current element 返回当前元素

  • Iterator::key — Return the key of the current element 返回当前元素的键

  • Iterator::next — Move forward to next element 移向下一个元素

  • Iterator::rewind — Rewind the Iterator to the first element 重新回到第一个元素

  • Iterator::valid — Checks if current position is valid 检查当前位置的有效性

咱不废话哈,直接来看下网上比较经典的一个实例:

class MyIterator implements Iterator
{
     private $var = array();
 
     public function __construct($array)
     {
         if (is_array($array)) {
            $this->var = $array;
         }
     }
 
     public function rewind() {
         echo "倒回第一个元素\n";
        reset($this->var);
     }
 
     public function current() {
        $var = current($this->var);
         echo "当前元素: $var\n";
         return $var;
     }
 
     public function key() {
        $var = key($this->var);
         echo "当前元素的键: $var\n";
         return $var;
     }
 
     public function next() {
        $var = next($this->var);
         echo "移向下一个元素: $var\n";
         return $var;
     }
 
 
     public function valid() {
        $var = $this->current() !== false;
         echo "检查有效性: {$var}\n";
         return $var;
     }
}
 
 
$values = array(1,2,3);
$it = new MyIterator($values);
foreach ($it as $k => $v) {
     print "此时键值对 -- key $k: value $v\n\n";
}

运行之后的结果如下:

我们可以想一下,如果把集合对象和对集合对象的操作放在一起,当我们想换一种方式遍历集合对象中元素时,就需要修改集合对象了,违背“单一职责原则”,而迭代器模式将数据结构和数据结构的算法分离开,两者可独立发展。

来看下迭代器的优点:

1.支持多种遍历方式。比如有序列表,我们根据需要提供正序遍历、倒序遍历两种迭代器。用户只需要得到我们的迭代器,就可以对集合执行遍历操作

2.简化了聚合类。由于引入了迭代器,原有的集合对象不需要自行遍历集合元素了

3.增加新的聚合类和迭代器类很方便,两个维度上可各自独立变化

4.为不同的集合结构提供一个统一的接口,从而支持同样的算法在不同的集合结构上操作

缺点就是迭代器模式将存储数据和遍历数据的职责分离增加新的集合对象时需要增加对应的迭代器类,类的个数成对增加,在一定程度上增加系统复杂度。

它的使用场景,我们可以参考如下几点:

1.访问一个聚合对象内容而无须暴露它的内部显示

2.需要为聚合对象提供多种遍历方式

3.为遍历不同的聚合结构提供一个统一的接口

我们要知道,最基本的迭代器接口是Iterator,来看下Iterator里面规范的方法:

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

完事,我们如果要进行遍历的类必须实现Iterator里面的抽象方法,如下: 

class Season implements Iterator{
    private $position = 0;//指针指向0
    private $arr = array('春','夏','秋','冬');
    public function rewind(){
        return $this -> position = 0;
    }
    public function current(){
        return $this -> arr[$this -> position];
    }
    public function key(){
        return $this -> position;
    }
    public function next() {
        ++$this -> position;
    }
 
    public function valid() {
        return isset($this -> arr[$this -> position]);
    }
}
$obj = new Season;
foreach ($obj as $key => $value) {
    echo $key.':'.$value."\n";
}

最后,咱们来看一个网上找的用迭代器模式来实现的一个斐波那契数列。

我们都知道,斐波那契数列通常做法是用递归实现,当然还有其它的方法,咱们这里用PHP的迭代器来实现一个斐波纳契数列,几乎没有什么难度,只是把类里的next()方法重写了一次。注释已经写到代码中,也是相当好理解的,如下:

class Fibonacci implements Iterator {
   private $previous = 1;
   private $current = 0;
   private $key = 0;
   
   public function current() {
       return $this->current;
   }
   
   public function key() {
       return $this->key;
   }
   
   public function next() {
       // 关键在这里
       // 将当前值保存到  $newprevious
       $newprevious = $this->current;
       // 将上一个值与当前值的和赋给当前值
       $this->current += $this->previous;
       // 前一个当前值赋给上一个值
       $this->previous = $newprevious;
       $this->key++;
   }
   
   public function rewind() {
       $this->previous = 1;
       $this->current = 0;
       $this->key = 0;
   }
   
   public function valid() {
       return true;
   }
}

$seq = new Fibonacci;
$i = 0;
foreach ($seq as $f) {
   echo "$f ";
   if ($i++ === 15) break;
}

输出的结果如下:

0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610

迭代器就是把不同的数据结构 "相同功能 "的函数装到一个名字相同的函数里, 无论你用的是什么结构(链表/数组/hash表等),总之你可以不关心任何细节遍历细节, 一些不可见的元素不会展示出来。 另外foreach是简单的循环语法,但迭代器是一种方法工具, 迭代器可以根据需要自定义读取数据,而foreach是一次性载入数据,从内存占用上讲迭代器也是更好的一种解决方式,当然具体实现还是要根据业务上的一些考量。