Press "Enter" to skip to content

关于一笔试题(Iterator模式)

中午的时候,收到一封求教信,是关于这样的一道面试题:

使对象可以像数组一样进行foreach循环,要求属性必须是私有。

刚接触到题的时候,我也没有考虑到Iterator模式,试了几个一般想法,失败以后。。。。就直接去翻看了foreach的源码实现,期望发现foreach处理对象的时候是否有什么特殊性,可以做为突破口。
跟踪了半天以后发现了核心逻辑中的一个奇怪的switch:

switch (zend_iterator_unwrap(array, &iter TSRMLS_CC)) {
        default:
        case ZEND_ITER_INVALID:
			.....
			break
        case ZEND_ITER_PLAIN_OBJECT: {
           	......
            break;
	case ZEND_ITER_PLAIN_ARRAY:
            .....
            break;
        case ZEND_ITER_OBJECT:
            ......
            break;
}

从这个结构,我们可以看到,对象分为ZEND_ITER_OBJECT和ZEND_ITER_PLAIN_OBJECT, 这是什么意思呢?

ZEND_API enum zend_object_iterator_kind zend_iterator_unwrap(
    zval *array_ptr, zend_object_iterator **iter TSRMLS_DC)
{
    switch (Z_TYPE_P(array_ptr)) {
        case IS_OBJECT:
            if (Z_OBJ_HT_P(array_ptr) == &iterator_object_handlers) {
                *iter = (zend_object_iterator *)zend_object_store_get_object(array_ptr TSRMLS_CC);
                return ZEND_ITER_OBJECT;
            }
            if (HASH_OF(array_ptr)) {
                return ZEND_ITER_PLAIN_OBJECT;
            }
            return ZEND_ITER_INVALID;
        case IS_ARRAY:
            if (HASH_OF(array_ptr)) {
                return ZEND_ITER_PLAIN_ARRAY;
            }
            return ZEND_ITER_INVALID;
        default:
            return ZEND_ITER_INVALID;
    }
}

这就要讲到PHP的内置接口Iterator了,PHP5开始支持了接口, 并且内置了Iterator接口, 所以如果你定义了一个类,并实现了Iterator接口,那么你的这个类对象就是ZEND_ITER_OBJECT,否则就是ZEND_ITER_PLAIN_OBJECT.
对于ZEND_ITER_PLAIN_OBJECT的类,foreach会通过HASH_OF获取该对象的默认属性数组,然后对该数组进行foreach.
而对于ZEND_ITER_OBJECT的类对象,则会通过调用对象实现的Iterator接口相关函数来进行foreach, 所以, 对于这道笔试题, 可以作出如下的答案:

class sample implements Iterator
{
    private $_items = array(1,2,3,4,5,6,7);
    public function __construct() {
                  ;//void
    }
    public function rewind() { reset($this->_items); }
    public function current() { return current($this->_items); }
    public function key() { return key($this->_items); }
    public function next() { return next($this->_items); }
    public function valid() { return ( $this->current() !== false ); }
}
$sa = new sample();
foreach($sa as $key => $val){
    print $key . "=>" .$val;
}

以上代码在我的php 5.3下运行正常。

32 Comments

  1. 面试题
    面试题 March 29, 2019

    看来懂看PHP源码很关键啊

  2. When you contacting Alexa Amazon, you have the option of sending an email, having Amazon call you, and live to chat. This article will show you how to talk with a customer service representative at Amazon. If you want to call Amazon directly, contact our customer service. Contacting Amazon Prime was a pure delight. We didn’t have to wait through a lengthy automated system; just a few options one being the customer support team.
    https://alexaamazo.com/

  3. alexa app
    alexa app March 15, 2019

    Amazon Alexa is a useful add-on for users who have Alexa devices. The app can provide the latest updates on what’s happening in the world, play your favourite songs, address your questions, make lists and more.

  4. 燕睿涛
    燕睿涛 July 28, 2015

    改为
    public function valid()
    {
    return $this->key() !== NULL;
    }

  5. 燕睿涛
    燕睿涛 July 28, 2015

    $t = [
    ‘a’ => ‘yrt’,
    ‘yrt’ => ‘燕睿涛’,
    3,
    false,
    ‘燕睿涛’
    ];
    循环输出有false的质的时候就会截断~~

  6. Jacob
    Jacob April 14, 2011

    最近在准备腾讯面试,看到了这个题,感觉到似乎有些问题?这样的实现只能对自己实现的迭代器遍历,且只能遍历封装好的数组属性,如果是一个已经定义好的类呢,里面的私有属性并不是封装在一个数组中,怎么遍历呢?

  7. php教程
    php教程 February 24, 2011

    这个设计模式,应该起来比较难

  8. phppan
    phppan April 24, 2010

    next方法在接口的定义中其返回值为void
    abstract public void next ( void )

  9. simaopig
    simaopig December 22, 2009

    话说我不知道SPL。。。残念

  10. Xiaoxiao
    Xiaoxiao June 8, 2009

    这个题,其实考的就是 PHP SPL吧,不用看源码吧…

  11. Jessica
    Jessica February 27, 2009

    class test {
    private $a = 1;
    private $b = 2;
    function getVars() {
    $array = get_defined_vars();
    return $array[‘this’];
    }
    }
    $test = new test();
    $testArr = $test->getVars();
    print_r($testArr);

  12. Jessica
    Jessica February 27, 2009

    getVars();
    print_r($testArr);

  13. alexsun
    alexsun February 22, 2009

    汗,这题是我出的,居然在这儿看到答案。。。面试题目要改一下了。

    • 雪候鸟
      雪候鸟 February 22, 2009

      @alexsun, 幸会,幸会~~

  14. laotan
    laotan December 10, 2008

    关于SPL的中文资料很少,只能顶着E文

  15. jackywdx
    jackywdx November 14, 2008

    呵呵,这段时间也正在研究PHP源代码,比较想了解PHP的内部实现。
    前几天正在想PHP是如何实现foreach循环的呢,正好搜到了。

  16. zvaly
    zvaly November 13, 2008

    foreach的原代码位置是哪里?楼主教一手吧 呵呵

    • 雪候鸟
      雪候鸟 November 13, 2008

      foreach的结构相对来说比较复杂,它不是一整块的代码块,
      它是在语法分析阶段, 做了一些工作.
      大体就是,在语法分析阶段(zend_language_parser.y)的时候,分别通过定位,foreach开始,中间代码,结束,从而设置出一个带有的循环的OPCODES序列..大体就是这样,如果有兴趣,可以看看上面提到的源文件.

  17. jcadam
    jcadam November 3, 2008

    实现一个完整的迭代器不是那么容易的事情。PHP这种弱类型语言的做法比较灵活。JAVA的实现就比较复杂;C++这种半弱不强的就更难一点。类型的POD,trivial判断,还有参数传导需要很多技巧……

  18. fy
    fy November 3, 2008

    果然很土

  19. 玉面修罗
    玉面修罗 November 1, 2008

    在Zend Framework源码中可以看到大量SPL的应用
    在看ZF代码之前我也确实还不知道居然有SPL这个东西

  20. blankyao
    blankyao October 31, 2008

    又要看源代码啊 🙂

    • laruence
      laruence November 1, 2008

      呵呵,绕了弯路,不过, 一切的一切都是可以在源码找到答案的,呵呵

  21. yzcj007
    yzcj007 October 31, 2008

    “对于ZEND_ITER_OBJECT的类对象,则会通过调用对象实现的Iterator接口相关函数来进行foreach”,这句话太关键了。
    经验主义害死人啊,非常感谢楼主,我看明白了。
    非常期待楼主出本PHP源码分析的书,从源码分析果然很好、很强大啊。

  22. 雪候鸟
    雪候鸟 October 31, 2008

    土了, 竟然不知道SPL,惭愧。

  23. ilsanbao
    ilsanbao October 31, 2008

    都是牛头哇!!!

  24. 神仙
    神仙 October 31, 2008

    哈哈
    其实直接去php手册搜会更快知道怎么弄
    不过这个php的源代码实现的似乎有点囧

    • 雪候鸟
      雪候鸟 October 31, 2008

      恩,我走了弯路,我还想着是不是可以通过其他的什么方式来更简单的实现。

Comments are closed.