gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-02 13:03:30 25 4
gpt4 key购买 nike

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

两种方法均完成相同的任务,一种方法是分配引用,另一种方法是根据 key 重新分配。我想在工作中使用良好的编程技术,但我想知道哪种方法是更好的编程实践?还是这其中的一件事并不重要?

最佳答案

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

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

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

    'Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.' — Brian Kernighan



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

    范围:
    首先,PHP并不是像C(++),C#,Java,Perl或ECMAScript6一样(不是很幸运)真正的块作用域……这意味着一旦循环发生,就不会取消设置$value变量完成的。当通过引用循环时,这意味着对您要迭代的任何对象/数组的最后一个值的引用都是 float 的。应该想到“等待发生的事故”这一短语。
    考虑以下代码中的$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调用并将其视为多余,这表明您被偏执,并一起删除它们。单靠注释并不能帮助您:需要阅读它们,并且应该对每个使用您的代码的人进行彻底的简要介绍,也许将它们添加为read a full article on the subject。链接文章中列出的示例很糟糕,但我看到的仍然更糟:
    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!
    }
    }

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

    副作用:
    人们常说函数不应该有副作用,因为(正确地)副作用被认为是代码异味。尽管foreach是一种语言构造,而不是一种函数,但是在您的示例中,应采用相同的思维方式。当使用太多引用时,您就太聪明了,可能会发现自己不得不单步执行循环,只是想知道什么变量在什么时候引用了什么,什么时候引用了。
    第一种方法没有这个问题:您拥有 key ,因此您知道自己在阵列中的位置。此外,使用第一种方法,您可以对该值执行任意数量的操作,而无需更改数组中的原始值(无副作用):
    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的初始值,但这只是愚蠢的(这增加了修复不应该的复杂性。 splinter 的):
    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次,您为该工作选择了错误的工具。

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

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

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

  • 关于php - 在foreach循环中,有什么更好的方法……使用&符号或基于键重新分配?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17459521/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com