使用引用来布置简单的功能是一种好习惯吗

Posted

技术标签:

【中文标题】使用引用来布置简单的功能是一种好习惯吗【英文标题】:Is it good practice to lay out even simple functions using references 【发布时间】:2012-11-30 04:05:57 【问题描述】:

我有一个函数:

$query = "SELECT * from lol";
database_query( $query );

考虑到$query 永远不会在database_query 函数中更改,使用指向$query 的指针是否是一种好习惯,这样函数就不需要为传入值的新迭代分配更多内存?

function database_query( &$query )
    //do stuff that does not affect $query

【问题讨论】:

您可能对这个最近的问题感兴趣:Why are references rarely used in php? 【参考方案1】:

不,不要这样做。如果在函数内部更改了 non-pass-by-reference 参数的值 ("copy on write"),PHP 只会创建另一个字符串副本。通过将参数设为引用,没有理由让阅读您的代码的人对您的函数正在执行的操作产生错误印象。

另外,references are not pointers。

【讨论】:

这是不正确的。 PHP 不会在写入时为引用变量进行复制。 @BennyHill:在谈到写时复制时,我指的不是引用变量。我已经为你澄清了这一点。 @BennyHill 当您的应用程序大于一个文件示例脚本时,您永远无法确定有多少不同的引用指向相同的值。您可能会从堆栈跟踪中较高位置的某个值触发写入时复制。 如果您使用您知道的参考,您肯定不会在写入时触发副本。【参考方案2】:

$query 的值是一个字符串。字符串没有内部可变状态——在 PHP 中“更改”包含字符串的变量的唯一方法是为其分配一个新字符串(或通过引用将其传递给一个函数,该函数然后为其分配一个新字符串, 等等。)。因此,当您执行$foo = $query; 时,任何合理的实现都会简单地将指针复制到内部字符数组,而不是复制实际字符,因为它们无法更改。所以即使你不知道 PHP 有写时复制,你也可以得出结论,按值传递字符串并不昂贵。

【讨论】:

【参考方案3】:

事实证明这是一个非常有趣的问题,我在过去的一个半小时里阅读了有关 PHP 以及它如何处理引用的信息(感谢 Tim Cooper 提供的链接让我开始学习)。

要回答您的问题,是的 - 在调用函数时使用这样的引用是一种很好的做法。通过使用引用,您将使用更少的资源 - 引用变量没有“写入时复制”。这里有一些证据:

<?php
function noref_nowrite($var_a)
    echo '<h3>NOT Using a Reference/Not Changing Data</h3>';
    echo '<p>'. xdebug_debug_zval('var_a') .'</p>';
    echo '<p>'. debug_zval_dump($var_a) .'</p>';
    echo '<p>$var_a = '. $var_a .' and $GLOBALS[a] = '. $GLOBALS['a'] .'</p>';

function noref_write($var_a)
    $var_a++;
    echo '<h3>NOT Using a Reference/Changing Data</h3>';
    echo '<p>'. xdebug_debug_zval('var_a') .'</p>';
    echo '<p>'. debug_zval_dump($var_a) .'</p>';
    echo '<p>$var_a = '. $var_a .' and $GLOBALS[a] = '. $GLOBALS['a'] .'</p>';


function ref_nowrite(&$var_a)
    echo '<h3>Using a Reference/Not Changing Data</h3>';
    echo '<p>'. xdebug_debug_zval('var_a') .'</p>';
    echo '<p>'. debug_zval_dump($var_a) .'</p>';
    echo '<p>$var_a = '. $var_a .' and $GLOBALS[a] = '. $GLOBALS['a'] .'</p>';

function ref_write(&$var_a)
    $var_a++;
    echo '<h3>Using a Reference/Changing Data</h3>';
    echo '<p>'. xdebug_debug_zval('var_a') .'</p>';
    echo '<p>'. debug_zval_dump($var_a) .'</p>';
    echo '<p>$var_a = '. $var_a .' and $GLOBALS[a] = '. $GLOBALS['a'] .'</p>';


$a = 5;
noref_nowrite($a);
noref_write($a);
ref_nowrite($a);
ref_write($a);
?>

如果您将上述代码复制/粘贴到 PHP 页面并执行它,您将看到:

NOT Using a Reference/Not Changing Data
var_a: (refcount=3, is_ref=0)=5
long(5) refcount(4)

$var_a = 5 and $GLOBALS[a] = 5


NOT Using a Reference/Changing Data
var_a: (refcount=1, is_ref=0)=6
long(6) refcount(2)
$var_a = 6 and $GLOBALS[a] = 5

Using a Reference/Not Changing Data
var_a: (refcount=3, is_ref=1)=5
long(5) refcount(1)
$var_a = 5 and $GLOBALS[a] = 5

Using a Reference/Changing Data
var_a: (refcount=3, is_ref=1)=6
long(6) refcount(1)
$var_a = 6 and $GLOBALS[a] = 6

所以我们这里有四个基本测试。我创建了一个全局变量 ($a) 并将其赋值为 5。

当我调用 noref_nowrite 函数时,我们看到 XDebug 计数为 3 个引用,而 PHP 的内置函数计数为 4。有趣的是,PHP 在内部对此进行了优化,因此它实际上就像调用 ref_nowrite 函数一样,因为 PHP 使 $var_a 成为对 $GLOBALS[' 的引用a']。

当我调用 noref_write 函数时,我们看到 refcount 下降到 1(如果您查看 PHP 的内置函数,则下降到 2)。为什么?因为这是发生“写时复制”问题的地方。在我们增加 $var_a 之前,PHP 在内部使用 $var_a 作为对 $a 的引用,但是当我们更改值时,我们强制 PHP 复制变量以便它可以增加。所以此时 $var_a 不再是对 $a 的引用,而是改为引用它自己的数据。

ref_nowrite 函数显示不明确的结果。单看它,我们无法证明任何事情。然而 ref_write 函数告诉我们 XDebug 说我们正在处理一个引用变量(is_ref=1),最重要的是我们看到在我们增加 $var_a 之后,我们的全局变量 $a 的值也发生了变化——这意味着 $var_a 和$GLOBALS['a'] 肯定指向内存中的同一个地方。这意味着更改 $var_a 不会触发“写入时复制”的情况 - 并且不应该因为我们正在处理引用。

玩弄这个来说服自己,这里还有一些阅读:

Detecting whether a PHP variable is a reference / referenced(我认为 ircmaxell 有一个深思熟虑的答案)

http://us2.php.net/debug-zval-dump

XDebug 文档:http://xdebug.org/docs/display

PHP 引用的作用:http://us3.php.net/manual/en/language.references.whatdo.php

PHP 引用计数基础知识:http://us3.php.net/manual/en/features.gc.refcounting-basics.php

【讨论】:

我不太确定这样做的目的是什么,因为问题不在于 PHP 是否使用引用变量执行写入时复制,它显然没有,而是它是否如果值未更改,则将参数作为参考传递是一种好习惯。您自己的测试表明 PHP 在未修改变量数据时确实会复制它,因此不应将变量作为引用传递。这只是在创建模棱两可的代码。 只是不要将某些东西作为参考传递,如果您不想要特定的行为,那不打算作为参考传递。首先它是(在这种情况下:超)微优化(我认为引擎可以做得更好),因此你只花了 90 分钟来找到一些东西,这没有任何好处。除此之外,问题是关于“良好做法”的,这绝对不是。

以上是关于使用引用来布置简单的功能是一种好习惯吗的主要内容,如果未能解决你的问题,请参考以下文章

通过 :: 调用包中的函数是一种好习惯吗

在 MySQL 的表名中使用点是一种好习惯吗

在 Perl 中,哈希键周围的引号是一种好习惯吗?

使用“视图”进行分组是一种好习惯吗?

C# 中的重载运算符实际上是一种好习惯吗? [关闭]

从循环内部返回是一种好习惯[关闭]