为什么通过引用在普通对象实例化和对象实例化之间存在差异?

时间:2013-02-08 23:30:43

标签: php reference porting

我正在开发一个从PHP 4迁移到PHP 5.2的极其古老的项目,现在我们正在将项目迁移到PHP 5.4。我们已经提出了许多参考错误。这是一些困扰我的示例代码。

<?php

class book_shelf 
{
    protected $_elements = array();

    function addBook(&$element){
        $this->_elements[] =& $element;
    }

    function printBooks(){
        print(json_encode($this->_elements));
    }
}

class book 
{
    function __construct($title, $author="unknown") {
        $this->title = $title;
        $this->author = $author;
    }

    public $title = NULL;
    public $author = NULL;
}

function createBookShelf(){
    $bookshelf1 = new book_shelf();

    for ($i = 0; $i < 3; $i++){
        $book1 = new book("Book $i");
        $bookshelf1->addBook($book1);
    }

    $bookshelf1->printBooks();  
}   

createBookShelf();

?>

我希望这可以创建3本独立的书籍。但是,elements数组中的每个对象最终都指向最后一个变量。另一方面,如果我通过引用创建一本新书,一切正常。 (例如$book1 =& new book("Book $i");)以下是示例代码:

<?php

class book_shelf 
{
    protected $_elements = array();

    function addBook(&$element) {
        $this->_elements[] =& $element;
    }

    function printBooks(){
        print(json_encode($this->_elements));
    }
}

class book 
{
    function __construct($title, $author="unknown") {
        $this->title = $title;
        $this->author = $author;
    }

    public $title = NULL;
    public $author = NULL;
}

function createBookShelf() {
    $bookshelf1 = new book_shelf();

    for ($i = 0; $i < 3; $i++){
        $book1 =& new book("Book $i");
        $bookshelf1->addBook($book1);
    }

    $bookshelf1->printBooks();  
}   

createBookShelf();

我的印象是在PHP 5.4中通过引用创建对象不是必需的。知道为什么我会得到不同的结果吗?

2 个答案:

答案 0 :(得分:2)

这是一个逻辑错误,与PHP版本无关。让我们看一下代码的以下片段:

//echo "phpinfo: " . phpinfo();
for ($i = 0; $i < 3; $i++){
    $book1 = new book("Book $i");
    $bookshelf1->addBook($book1);
}

...

function addBook(&$element){
    $this->_elements[] =& $element;
}

发生了什么事?您通过引用传递$ book。但是在每个循环中,您都会更改引用的值。在for循环结束时,$ book指向最后创建的book对象,因此引用。 (也有过这个错误;)

结论:您的addBook函数必须如下所示:

function addBook($element){
    $this->_elements[] = $element;
}

并保留添加图书的代码不变 - 就像上面的例子中没有&一样。


* 关于=&amp;新... *

首先,如果您这样做,您将从php 5.3开始收到以下警告:

  

不推荐使用:不推荐使用引用分配new的返回值

因此,您不应将其用于新代码。

答案 1 :(得分:1)

在您编写时,这最初是PHP 4代码。这两个结构:

$book1 =& new book("Book $i");

function addBook(&$element){
    $this->_elements[] =& $element;
}

不再需要了。第一个是“new by reference”,这对于从来没有正确做到但是必须要在PHP 4中使用对象而不是一直克隆对象

第二个是“通过引用传递”这可能是一个有效的概念,但就PHP 5 中的对象而言,这不是必需的,不应该使用对于作为对象的参数。

内存管理在PHP 5中得到了极大的改进,然后又在PHP 5.3中得到了很大的改进。 PHP 5.4通过提供警告帮助您迁移代码。特别是“引用新的”可以很容易地被发现和修复。

旧(PHP 4):

$book1 =& new book("Book $i");

新(当前PHP版本):

$book1 = new book("Book $i");

(不,它不会回来:))。对于通过引用传递的方法,我建议您在重构代码时删除引用,并键入提示对象类型:

旧(PHP 4):

function addBook(&$element){
    $this->_elements[] =& $element;
}

新的(当前的PHP版本):

function addBook(Book $element){
    $this->_elements[] = $element;
}

然后你不仅修复了一个旧问题,而且还确保只使用预期的类型调用此函数,你可以看到它总是意味着获取一个对象(而不是仅在PHP 4中使用的变量引用)对于对象,因为PHP 4是有限的。这不再需要了,并且可以(并且应该扔掉这个旧的!)去除。)

将您的代码库置于版本控制之下,这样您就可以轻松地将更改应用到代码库中,而不必破坏以前工作的任何内容。


  

我希望这可以创建3本独立的书籍。但是,element数组中的每个对象最终都指向最后一个变量。

这里的实际问题是您使用pass by reference。分配

$book1 = new book("Book $i");

$book1的引用计数为1,未标记为引用。现在将它作为参考传递给$bookshelf1->addBook($book1)方法会将其变为引用,引用的引用数为2,因为在该方法中,引用再次分配给类的私有成员:

for ($i = 0; $i < 3; $i++)
{
    $book1 = new book("Book $i");
    $bookshelf1->addBook($book1);
}

...

function addBook(&$element){
    $this->_elements[] =& $element;
}

然后更改现在创建的引用。

现在,当您将实例化的代码更改为:

$book1 = & new book("Book $i");

你总是 - 每个循环一次 - 只用相同的变量名创建一个新的别名(引用)。因为您将此作为参考传递,所以它是您指定为参考的新别名。因此, Book 中的数组指向三个不同的引用 - 而不是相同引用的三倍。

我希望这能更好地解释这个问题。这就是为什么我们说:除非你知道你做什么,否则不要使用引用。 PHP 4的情况是许多用户过去常常使用引用而没有完全理解它是如何工作的(这是可以理解的,因为它在PHP中并不容易,我还需要先了解它,直到我找到一个好的描述在这里分享 - 希望这次工作:))。这里使用PHP 5的好处是,您不再需要使用对象的引用,这样可以更容易地编写和读取代码。


相关: