当前位置: 首页 > 面试题库 >

在foreach循环中,有什么更好的方法……使用&符号或基于键重新分配?

杜弘光
2023-03-14
问题内容

考虑以下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);

两种方法都可以完成相同的任务,一种方法是分配参考,另一种方法是根据密钥重新分配。我想在工作中使用良好的编程技术,但我想知道哪种方法是更好的编程实践?还是这其中的一个并不重要?


问题答案:

由于得分最高的答案表明第二种方法在各个方面都更好,因此我不得不在此处发布答案。诚然,通过引用循环 更好的性能,但它并非没有风险/陷阱。
一如既往的底线: “ X或Y哪个更好” ,您可以获得的唯一真实答案是:

  • 这取决于你在做什么/在做什么
  • 哦,两个都很好,如果你知道自己在做什么
  • X有利于 此类 ,Y有利于 So
  • 不要忘了Z,即使这样…( “ X,Y或Z更好” 是相同的问题,因此适用相同的答案:取决于,如果…都可以)

如Orangepill所示,参考方法可以提供更好的性能。在这种情况下,性能和代码之间的权衡之一是不容易出错,更易于阅读/维护。通常,最好选择更安全,更可靠和更可维护的代码:

调试的难度是一开始编写代码的两倍。因此,如果您尽可能聪明地编写代码,那么就定义而言,您就不足以调试它。—布赖恩·克尼根(Brian Kernighan)

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

范围:
首先,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呼叫并认为它们多余,这表明您被偏执,并一起删除它们。仅凭注释就无济于事:它们必须被阅读,并且与您的代码一起工作的每个人都应被彻底介绍,也许让他们阅读了有关该主题的完整文章。链接文章中列出的示例很糟糕,但我看到的仍然更糟:

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!
    }
}

这是一个旅行价值问题的简单示例。顺便说一句,我没有弥补这一点,顺便说一下。除了发现bug和理解代码(参考文献使之变得更加困难)外,在此示例中它仍然很明显,主要是因为即使使用宽敞的Allman编码样式,它也只有15行长…现在,想象一下这个基本构造用于代码中,实际上
它所做的 事情甚至稍微复杂一些,也有意义。祝您调试顺利。

副作用:
经常说函数不应该有副作用,因为(正确地)副作用被认为是 代码异味
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]

为了解决这个问题,我们要么必须创建一个临时变量,要么调用函数twwce,或者添加一个键,然后重新计算的初始值$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次,您为该工作选择了错误的工具。

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

注意:
从5.4开始,在调用时通过引用传递已被删除。厌倦可能会发生变化的功能。数组的标准迭代多年没有改变。我猜这就是您所谓的 “成熟技术”
。它按照锡罐上的指示进行操作,是做事的更安全方式。那如果慢一点呢?如果速度是一个问题,则可以优化代码,然后引入对循环的引用。
编写新代码时,请使用易于阅读,最故障保护的选项。优化可以(而且确实 应该 )等待,直到一切都经过尝试和测试。

一如既往: 过早的优化是万恶之源 。并且 选择适合该工作的工具,而不是因为它是新奇的



 类似资料:
  • 我有以下数组: 我通过foreach循环得到结果: 现在我想用逗号分隔每个输出。有些可能是空的。 我读过php方法,但我不确定在这种情况下如何使用它,或者是否有更好的解决方案。

  • 问题内容: 如果我有一个数字列表,并且想使用for循环将其递增,那为什么不起作用: 我期望得到[2,3,4,5,6]。 我知道我可以通过解决这个问题 但是必须有一种更简单的方法。 问题答案: 这是因为您仅获得列表中变量的引用。替换它时,只需更改该引用指向的内容,因此列表中的项目保持不变。对此的最佳解释是直观地进行跟踪-逐步执行该链接处的执行可视化,并且应该非常清楚。 请注意,可变对象可以在适当位置

  • 当我们基于某个键在流上应用组 by 函数时,kafka 如何计算这一点,因为相同的键可能存在于不同的分区中?我看到了()函数,它基本上对数据进行了重新分区,但我不明白它是什么意思。它会将具有相同键的所有消息移动到单个分区中吗?另外,我们可以通过()方法调用的频率如何?如果有要求,我们可以在收到每条消息后调用它吗?请建议。谢谢

  • 问题内容: 我可以使用分号在Python中加入行,例如 但是为什么我不能这样做 问题答案: 简短的(至今有效的)答案只是“因为未定义语言语法以允许它”。至于为什么 这是 的话,就很难,如果除非你问任何人与语法的部分来了,但我想这是由于可读性,这是Python的目标之一并非不可能,以确保1。 你为什么要写像这样晦涩难懂的东西?只需将其分成多行: 我认为这种变体要清晰得多。 1自: 可读性计数。

  • 数据结构如下: 转介及统计数字 现在,对于这个表,我想插入两个数组,第一个数组将成功插入,如下所示: 现在第二个数组是$userWriterReferralStatsArray,在插入这个数组时,我想检查是否重复了相同的日期和相同的注册用户id,然后它应该更新,而不是插入新行。我如何实现它,或者请建议我是否有其他好的方法来实现它,因为有大量的行。 提前感谢

  • 问题内容: 我有一个ng-repeat返回对象数组,如下所示: 我想拉出对象并将其推入另一个数组,以便将其格式化为: 目标是在数组上使用orderBy。是否可以将JSON重组为这种格式,然后访问数据? 这是我的观点供参考: 我的JSON格式: 问题答案: 只是详细说明我的回答:- 您可以通过这种方式将各个月分散的阵列合并为1个阵列。 ==>将给您属性中的月份到一个数组 ==>您传入月份数组,然后从