msgbartop
PHP源码分析,Zend引擎分析,Web相关技术研究,Web技术分享–左手代码 右手诗
msgbarbottom

31 Oct 08 关于一笔试题(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下运行正常。

Related Posts:

Tags: , , ,

Reader's Comments

  1. |

    哈哈
    其实直接去php手册搜会更快知道怎么弄

    不过这个php的源代码实现的似乎有点囧

  2. |

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

  3. |

    都是牛头哇!!!

  4. |

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

  5. |

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

  6. |

    又要看源代码啊 :)

  7. |

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

  8. |

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

  9. |

    果然很土

  10. |

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

  11. |

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

  12. |

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

  13. |

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

  14. |

    顶!

  15. |

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

  16. |

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

  17. |

    @alexsun, 幸会,幸会~~

  18. |

    getVars();
    print_r($testArr);

  19. |

    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);

  20. |

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

  21. |

    文章不错

  22. |

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

Leave a Comment

*
To prove you're a person (not a spam script), type the security word shown in the picture. Click on the picture to hear an audio file of the word.
Click to hear an audio file of the anti-spam word