在 foreach 循环中,使用 & 符号或基于键重新分配哪个更好?

2024-04-03

考虑以下 PHP 代码:

//Method 1
$array = array(1,2,3,4,5);
foreach($array as $i=>$number){
  $number++;
  $array[$i] = $number;
}
print_r($array);


//Method 2
$array = array(1,2,3,4,5);
foreach($array as &$number){
  $number++;
}
print_r($array);

两种方法都完成相同的任务,一种通过分配引用,另一种通过基于键重新分配。我想在工作中使用良好的编程技术,但我想知道哪种方法是更好的编程实践?或者这是一件并不重要的事情?


由于得分最高的答案表明第二种方法在各方面都更好,因此我觉得有必要在这里发布答案。正确,按引用循环is性能更高,但并非没有风险/陷阱。
底线,一如既往:“X 和 Y 哪个更好”,你能得到的唯一真正的答案是:

  • 这取决于你在追求什么/你在做什么
  • 哦,两者都可以,如果你知道自己在做什么
  • X 适合Such, Y 更适合So
  • 别忘了Z,即便如此......(“X、Y 或 Z 哪个更好”是同一个问题,所以适用相同的答案:这取决于,如果......)

尽管如此,正如 Orangepill 所表明的那样,参考方法提供了更好的性能。在这种情况下,性能与不易出错、更易于阅读/维护的代码之间的权衡之一。一般来说,最好选择更安全、更可靠、更可维护的代码:

“调试的难度是最初编写代码的两倍。因此,如果您尽可能巧妙地编写代码,那么根据定义,您就不够聪明,无法调试它。 — 布莱恩·科尼汉

我想这意味着必须考虑第一种方法最佳实践。但这并不意味着应该始终避免第二种方法,因此下面是在引用中使用引用时必须考虑的缺点、陷阱和怪癖。foreach loop:

Scope:
首先,PHP 并不像 C(++)、C#、Java、Perl 或(运气好的话)ECMAScript6 那样真正具有块作用域...这意味着$value多变的不会被取消设置一旦循环完成。当按引用循环时,这意味着对您正在迭代的任何对象/数组的最后一个值的引用正在浮动。词组“一场等待发生的事故”应该会浮现在脑海中。
考虑会发生什么$value,随后$array,在下面的代码中:

$array = range(1,10);
foreach($array as &$value)
{
    $value++;
}
echo json_encode($array);
$value++;
echo json_encode($array);
$value = 'Some random value';
echo json_encode($array);

该片段的输出将是:

[2,3,4,5,6,7,8,9,10,11]
[2,3,4,5,6,7,8,9,10,12]
[2,3,4,5,6,7,8,9,10,"Some random value"]

换句话说,通过重用$value变量(引用数组中的最后一个元素),您实际上是在操作数组本身。这使得代码容易出错并且调试困难。相对于:

$array = range(1,10);
$array[] = 'foobar';
foreach($array as $k => $v)
{
    $array[$k]++;//increments foobar, to foobas!
    if ($array[$k] === ($v +1))//$v + 1 yields 1 if $v === 'foobar'
    {//so 'foobas' === 1 => false
        $array[$k] = $v;//restore initial value: foobar
    }
}

可维护性/防白痴:
当然,您可能会说悬空引用是一个简单的修复方法,您是对的:

foreach($array as &$value)
{
    $value++;
}
unset($value);

但是,在您使用引用编写了前 100 个循环之后,您真的相信您不会忘记取消设置单个引用吗?当然不是!这是如此罕见unset已在循环中使用的变量(我们假设 GC 会为我们处理它),所以大多数时候,您不必打扰。当涉及到引用时,这会是令人沮丧、神秘的错误报告或旅行价值,您使用复杂的嵌套循环,可能有多个引用......恐怖,恐怖。
此外,随着时间的推移,谁敢说下一个处理你的代码的人不会忘记unset?谁知道呢,他可能甚至不知道参考资料,或者看到你的无数unset打电话并认为它们是多余的,这是你偏执的表现,然后将它们全部删除。单独的注释对你没有帮助:它们需要被阅读,并且每个使用你的代码的人都应该被彻底介绍,也许有它们阅读有关该主题的完整文章 http://schlueters.de/blog/archives/141-References-and-foreach.html。链接文章中列出的示例很糟糕,但我还见过更糟糕的:

foreach($nestedArr as &$array)
{
    if (count($array)%2 === 0)
    {
        foreach($array as &$value)
        {//pointless, but you get the idea...
            $value = array($value, 'Part of even-length array');
        }
        //$value now references the last index of $array
    }
    else
    {
        $value = array_pop($array);//assigns new value to var that might be a reference!
        $value = is_numeric($value) ? $value/2 : null;
        array_push($array, $value);//congrats, X-references ==> traveling value!
    }
}

这是旅行价值问题的一个简单示例。顺便说一句,这不是我编造的,我遇到过可以归结为这一点的代码......老实说。除了发现错误和理解代码(参考资料使这变得更加困难)之外,在这个例子中它仍然相当明显,主要是因为它只有 15 行长,即使使用宽敞的 Allman 编码风格......现在想象一下这个基本结构被用在实际的代码中does甚至稍微复杂一点、更有意义的东西。祝你调试顺利。

副作用:
人们常说函数不应该有副作用,因为副作用(正确地)被认为是代码气味。尽管foreach是一种语言构造,而不是函数,在您的示例中,应该应用相同的思维方式。当使用太多引用时,你就太聪明了,不利于自己,并且可能会发现自己必须单步执行循环,只是为了知道什么变量在何时引用什么。
第一种方法没有这个问题:你有密钥,所以你知道你在数组中的位置。更重要的是,使用第一种方法,您可以对值执行任意次数的操作,而无需更改数组中的原始值(无副作用):

function recursiveFunc($n, $max = 10)
{
    if (--$max)
    {
        return $n === 1 ? 10-$max : recursiveFunc($n%2 ? ($n*3)+1 : $n/2, $max);
    }
    return null;
}
$array = range(10,20);
foreach($array as $k => $v)
{
    $v = recursiveFunc($v);//reassigning $v here
    if ($v !== null)
    {
        $array[$k] = $v;//only now, will the actual array change
    }
}
echo json_encode($array);

这会生成输出:

[7,11,12,13,14,15,5,17,18,19,8]

正如您所看到的,第一、第七和第十元素已更改,其他元素没有更改。如果我们使用引用循环重写这段代码,循环看起来会小很多,但输出会有所不同(我们有一个副作用):

$array = range(10,20);
foreach($array as &$v)
{
    $v = recursiveFunc($v);//Changes the original array...
    //granted, if your version permits it, you'd probably do:
    $v = recursiveFunc($v) ?: $v;
}
echo json_encode($array);
//[7,null,null,null,null,null,5,null,null,null,8]

为了解决这个问题,我们要么必须创建一个临时变量,要么调用函数tiwce,要么添加一个键,然后重新计算初始值$v,但这简直是愚蠢的(这增加了修复不应该被破坏的东西的复杂性):

foreach($array as &$v)
{
    $temp = recursiveFunc($v);//creating copy here, anyway
    $v = $temp ? $temp : $v;//assignment doesn't require the lookup, though
}
//or:
foreach($array as &$v)
{
    $v = recursiveFunc($v) ? recursiveFunc($v) : $v;//2 calls === twice the overhead!
}
//or
$base = reset($array);//get the base value
foreach($array as $k => &$v)
{//silly combine both methods to fix what needn't be a problem to begin with
    $v = recursiveFunc($v);
    if ($v === 0)
    {
        $v = $base + $k;
    }
}

不管怎样,添加分支、临时变量和你拥有的东西,反而违背了这一点。首先,它引入了额外的开销,这将削弱参考文献最初为您提供的性能优势。
如果您必须向循环添加逻辑,以修复不需要修复的内容,那么您应该退后一步,考虑一下您正在使用的工具。 9/10 次,你选择了错误的工具来完成这项工作。

至少对我来说,第一种方法的最后一个令人信服的论据很简单:可读性。参考运算符 (&)如果您正在做一些快速修复或尝试添加功能,则很容易被忽视。您可能会在运行良好的代码中创建错误。更重要的是:因为它工作正常,您可能不会彻底测试现有功能because没有已知问题。
由于您忽视操作员而发现投入生产的错误可能听起来很愚蠢,但您不会是第一个遇到这种情况的人。

Note:
自 5.4 起,调用时通过引用传递已被删除。对可能发生变化的特性/功能感到厌倦。数组的标准迭代多年来没有改变。我想这就是你可以这么称呼的“经过验证的技术”。它按照其承诺进行操作,并且是更安全的做事方式。那么如果速度慢了怎么办?如果速度是一个问题,您可以优化代码,然后引入对循环的引用。
编写新代码时,请选择易于阅读、最安全的选项。优化可以(而且确实should)等到一切都经过尝试和测试。

一如既往:过早的优化是万恶之源. And 选择适合工作的工具,而不是因为它是新的、闪亮的.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 foreach 循环中,使用 & 符号或基于键重新分配哪个更好? 的相关文章

  • 在 PHP 中模拟 jQuery.ajax 请求

    我必须在 PHP 中模拟 AJAX 请求 就像在 jQuery 中一样 我当前的代码在这里 原始 AJAX 调用 不得修改 ajax type POST url someFile php data data success function
  • “pdo_mysql”已禁用,我无法启用它。我在 iMac 7.1 OSX 10.6.8 上安装了 MAMP v. 3.0.4

    pdo mysql 已禁用 我无法启用它 我在 iMac 7 1 OSX 10 6 8 上安装了 MAMP v 3 0 4 在我的 phpinfo 页面上 我可以看到唯一启用的 PDO 是 sqlite 如果我查看 php 5 5 10 扩
  • 来自本地 XML 的模拟 SoapClient 响应

    我想用文件中的 XML 来模拟 SoapClient 的响应 我如何创建一个 stdClass 对象 就像 SoapClient 从文件返回一样 客户端已经包装了 SoapClient 因此可以轻松模拟响应 我的模拟是这样的 soapCli
  • JavaScript 中带前导零的数字发生变化

    我使用 print 语句从 php 调用 javascript 函数来打印 html 代码 并且传入一个整数 但是 在 php 中传递的值与 javascript 函数接收到的数字不匹配 我不知道为什么 这是调用 javascript 函数
  • PHP、jQuery 和 Ajax 调用乱序

    我正在使用 jQuery 进行 Ajax 调用 我有 x 数量的 Ajax 调用附加到 div 这些 Ajax 加载请求是由 PHP foreach 循环生成的 问题是它们渲染的顺序不正确 它们被设置在数组中
  • 一段 R 代码会影响 foreach 输出中的随机数吗?

    我使用运行模拟foreach and doParallel并与随机数 名为random在代码中 简而言之 我模拟一个足球联赛 随机生成所有比赛的获胜者以及相应的结果 在dt base没有比赛进行 在dt ex1 and dt ex24场比赛
  • 创建 Facebook 测试用户时访问令牌出现问题

    我正在尝试为我的 Facebook 应用程序创建测试用户 他们在 11 月份的博客文章 http developers facebook com blog post 429 中宣布了此功能 并在此处记录了该功能 http developer
  • Doctrine 1 和 Symfony 1 的多个主键?

    我已经知道在 Symfony 1 和 Doctrine 1 中不可能使用多个主键 但是你们知道有什么好的解决方法吗 除了多对多关系之外 原则 1 不适用于多列上的主键 但如果你想使用多对多关系 请像这样使用 BlogPost columns
  • Yii2 中 init() 和 __construct() 方法有什么区别

    init 方法 public function init construct method public function construct 那么 它们之间有什么区别 应该使用哪一个呢 init 是从以下对象扩展的任何对象的方法yii b
  • chown:不允许操作

    我有问题 我需要通过 php 脚本为系统中的不同用户设置文件所有者权限 所以我通过以下命令执行此操作 其中 1002 是系统的用户 ID file put contents filename content system chown 100
  • 从 n,k 维矩阵数组中减去 n,k 维矩阵

    如果我有一个数组A A lt array 0 c 4 3 5 for i in 1 5 set seed i A i lt matrix rnorm 12 4 3 如果我有矩阵 B set seed 6 B lt matrix rnorm
  • 如何解决 Laravel 8 UI 分页问题?

    我在尝试最近发布的 laravel 8 时遇到了问题 我试图找出变化是什么以及它是如何工作的 当我这样做时 我遇到了分页 laravel 8 UI 变得混乱的问题 不知何故它发生了 有人可以帮助我吗 或者经历过同样的事情 像这样我在 lar
  • 如何让Gmail像加载进度条一样

    我想在页面的中心和顶部创建一个像 Gmail 一样的加载进度条 并适用于所有浏览器 这是基本代码
  • PHP:在脚本完成之前获取输出

    我有一个名为 data php 的脚本 如下所示 do some stuff echo result do some other stuff eg database operations 我需要在另一个脚本中使用 data php 的输出
  • 简单的dom php解析获取自定义数据属性值

    HTML div class something ddsf PHP foreach dom gt find something data rel as this var dump this gt attr 我尝试了这个但错误 在其文档中找不
  • Minizinc:生成有效的转变

    希望有人能帮助我解决这个问题 最初的问题是生成有效的班次 如下所述 我有这样的数组 m m m o o l l m m m l m m m 具有固定长度 S 其中 m 是工作 o 是办公室 我自由了 我需要确保至少每 6m 就有两个 l 在
  • 合并 url 中的 2 个输入值

    我有这样的形式
  • Doctrine DQL 从 join 返回平面数组

    我通过 DQL 中的常规 LEFT JOIN 选择 3 个实体 它们通过连接表关联 连接表还定义了实体以及带注释的关系 查询执行没有问题 但我的结果作为平面数组返回 我期望一个包含三个实体作为每个索引的数组元素的数组 SELECT e1 e
  • 使用来自另一个数据库的选择查询更新 mysql 表

    我有两个数据库 我想用另一个数据库表中的值更新一个表 我正在使用以下查询 但它不起作用 UPDATE database1 table1 SET field2 database2 table1 field2 WHERE database1 t
  • 使用随机放置的 NaN 创建示例 numpy 数组

    出于测试目的 我想创建一个M by Nnumpy 数组与c随机放置的 NaN import numpy as np M 10 N 5 c 15 A np random randn M N A mask np nan 我在创建时遇到问题mas

随机推荐