Ответ 1
Поскольку самый высокий результат подсчета голосов говорит о том, что второй метод лучше во всех отношениях, я чувствую себя обязанным публиковать ответ здесь. Правда, цикл по ссылке более результативен, но он не лишен рисков/ловушек.
Итог, как всегда: "Что лучше X или Y", единственные реальные ответы, которые вы можете получить, следующие:
- Это зависит от того, что вы делаете/что вы делаете.
- О, оба в порядке, если вы знаете, что делаете.
- X хорош для таких, Y лучше для So
- Не забывайте о Z, и даже тогда... ( "лучше X, Y или Z" - это тот же вопрос, поэтому применяются те же ответы: это зависит, оба нормально, если...)
Как бы это ни было, как показал Orangepill, эталонный подход обеспечивает лучшую производительность. В этом случае компромисс между одним из показателей производительности и кодом, который менее подвержен ошибкам, легче читать /maintan. В целом, он считал, что лучше пойти на более безопасный, надежный и более удобный код:
'Отладка в два раза сложнее, чем писать код в первую очередь. Поэтому, если вы пишете код настолько умно, насколько это возможно, вы по определению недостаточно умны, чтобы его отлаживать ". - Брайан Керниган
Я предполагаю, что это означает, что первый метод следует считать лучшей практикой. Но это не означает, что во всех случаях следует избегать второго подхода, поэтому ниже следует использовать недостатки, подводные камни и причуды, которые вы должны учитывать при использовании ссылки в цикле foreach
:
Область применения:
Для начала PHP не является действительно блочным облаком, как C (++), С#, Java, Perl или (с удачей) ECMAScript6... Это означает, что переменная $value
не будет отменена, как только цикл закончен. При циклировании по ссылке это означает, что ссылка на последнее значение любого объекта/массива, который вы повторяли, плавает. Фраза "авария, ожидающая случиться" должна spring мыслить.
Рассмотрим, что происходит с $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!
}
}
Это простой пример проблемы стоимости движения. Я не делал этого, кстати, я нашел код, который сводится к этому... честно. В отличие от определения ошибки и понимания кода (который был затруднен ссылками), в этом примере он все еще довольно очевиден, главным образом потому, что он составляет всего 15 строк, даже используя просторный стиль кодирования Allman... Теперь представьте, что эта базовая конструкция используется в коде, что на самом деле делает что-то еще более сложное и содержательное. Удачи, отлаживая это.
побочные эффекты:
Часто говорилось, что функции не должны иметь побочных эффектов, потому что побочные эффекты (по праву) считаются кодовым запахом. Хотя 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 раз вы выбрали неправильный инструмент для задания.
Последнее, что для меня, по крайней мере, является убедительным аргументом для первого метода, просто: читаемость. Справочный оператор (&
) легко упускается из виду, если вы выполняете некоторые быстрые исправления или пытаетесь добавить функциональность. Вы можете создавать ошибки в коде, который работал очень хорошо. Что еще: потому что он работает нормально, вы не можете тестировать существующую функциональность так же тщательно, потому что не было известных проблем.
Обнаружение ошибки, появившейся в процессе производства, из-за того, что вы игнорируете оператора, может показаться глупым, но вы бы не первый, кто столкнулся с этим.
Примечание:
Передача по ссылке во время вызова была удалена с 5.4. Утомляйтесь функциями/функциями, которые могут быть изменены. стандартная итерация массива не изменилась годами. Я предполагаю, что вы могли бы назвать "проверенной технологией". Он делает то, что он говорит на жестяной основе, и является более безопасным способом делать вещи. Так что, если это медленнее? Если скорость является проблемой, вы можете оптимизировать свой код и затем вводить ссылки на свои циклы.
При написании нового кода перейдите к легко читаемому, наиболее отказоустойчивому варианту. Оптимизация может (и действительно должна) ждать, пока все не будет проверено и проверено.
И как всегда: преждевременная оптимизация - это корень всего зла. И выберите правильный инструмент для работы, а не потому, что он новый и блестящий.