Подтвердить что ты не робот

Почему я должен использовать foreach вместо for (int я = 0; я <length; я ++) в циклах?

Кажется, что классный цикл цикла на С# и Java - использовать foreach вместо стиля C для циклов.

Есть ли причина, почему я предпочитаю этот стиль над стилем C?

Меня особенно интересуют эти два случая, но, пожалуйста, обращайтесь к большему количеству случаев, когда вам нужно объяснить свои баллы.

  • Я хочу выполнить операцию над каждым элементом в списке.
  • Я ищу элемент в списке и хочу выйти, когда этот элемент будет найден.
4b9b3361

Ответ 1

Я могу подумать о двух основных причинах:

1) Он абстрагируется от основного типа контейнера. Это означает, например, что вам не нужно менять код, который перемещается по всем элементам в контейнере при изменении контейнера - вы указываете цель "сделать это для каждого элемента в контейнере", а не средства.

2) Это устраняет возможность ошибок "один за другим".

С точки зрения выполнения операции над каждым элементом в списке, интуитивно просто сказать:

for(Item item: lst)
{
  op(item);
}

Он прекрасно выражает намерение читателя, в отличие от ручной работы с итераторами. То же самое для поиска предметов.

Ответ 2

Представьте, что вы главный шеф-повар ресторана, и вы все готовите огромный омлет для буфета. Вы раздаете карточку из дюжины яиц каждому из двух сотрудников кухни и говорите им, чтобы они получили растрескивание буквально.

Первый устанавливает чашу, открывает ящик, захватывает каждое яйцо по очереди - слева направо через верхний ряд, затем нижний ряд - ломает его по бокам чаши, а затем опустошает его в чашу, В конце концов у него заканчиваются яйца. Хорошо выполненная работа.

Второй устанавливает чашу, открывает ящик, а затем бросается, чтобы получить лист бумаги и ручку. Он пишет цифры от 0 до 11 рядом с веткими картонной коробки и номером 0 на бумаге. Он смотрит на номер на бумаге, находит отсек с надписью 0, удаляет яйцо и трескает его в миску. Он снова смотрит на 0 на бумаге, думает "0 + 1 = 1", пересекает 0 и записывает 1 на бумаге. Он хватает яйцо из купе 1 и трескает его. И так далее, пока номер 12 не появится на бумаге, и он знает (не глядя!), Что яиц больше нет. Хорошо выполненная работа.

Вы думаете, что второй парень был немного испорчен в голову, верно?

Точка работы на высокоуровневом языке заключается в том, чтобы избежать необходимости описывать вещи в компьютерных терминах и быть в состоянии описать их в ваших собственных терминах. Язык более высокого уровня, тем более верно это. Увеличение счетчика в цикле является отвлечением от того, что вы действительно хотите сделать: обрабатывать каждый элемент.


Кроме того, структуры типа связанных списков не могут быть обработаны эффективно, увеличивая счетчик и индексируя в: "индексирование" означает начало с подсчета с самого начала. В C мы можем обработать связанный список, который мы сделали сами, используя указатель на цикл "counter" и разыменовывая его. Мы можем сделать это в современном С++ (и в некоторой степени на С# и Java) с использованием "итераторов", но это все еще страдает от проблемы косвенности.


Наконец, некоторые языки имеют достаточно высокий уровень, что идея фактически писать цикл для "выполнения операции над каждым элементом в списке" или "поиск элемента в списке" ужасно (так же, как шеф-повар не должен рассказывать первому кухонному сотруднику, как обеспечить, чтобы все яйца треснули). Предоставляются функции, которые настраивают эту структуру цикла, и вы сообщаете им - через функцию более высокого порядка или, возможно, сравнительное значение в случае поиска - что делать в цикле. (Фактически, вы можете делать эти вещи на С++, хотя интерфейсы несколько неуклюжи.)

Ответ 3

  • foreach более прост и читаем

  • Он может быть более эффективным для построений, таких как связанные списки

  • Не все коллекции поддерживают произвольный доступ; единственный способ итерации a HashSet<T> или Dictionary<TKey, TValue>.KeysCollection - foreach.

  • foreach позволяет выполнять итерацию по коллекции, возвращенной методом без дополнительной временной переменной:

    foreach(var thingy in SomeMethodCall(arguments)) { ... }
    

Ответ 4

Одно из преимуществ для меня заключается в том, что менее легко совершать ошибки, такие как

    for(int i = 0; i < maxi; i++) {
        for(int j = 0; j < maxj; i++) {
...
        }
    }

UPDATE: Это один из способов устранения ошибки. Я делаю сумму

int sum = 0;
for(int i = 0; i < maxi; i++) {
    sum += a[i];
}

а затем решите собрать его больше. Поэтому я завершаю цикл в другом.

int total = 0;
for(int i = 0; i < maxi; i++) {
   int sum = 0;
    for(int i = 0; i < maxi; i++) {
        sum += a[i];
    }
    total += sum;
}

Скомпилировать не удается, конечно, поэтому мы вручную редактируем

int total = 0;
for(int i = 0; i < maxi; i++) {
   int sum = 0;
    for(int j = 0; j < maxj; i++) {
        sum += a[i];
    }
    total += sum;
}

В коде есть, по крайней мере, две ошибки (и многое другое, если мы запутались maxi и maxj), которые будут обнаружены только при ошибках во время выполнения. И если вы не пишете тесты... и это редкая часть кода - это укусит кого-то ELSE - плохо.

Вот почему полезно извлечь внутренний цикл в метод:

int total = 0;
for(int i = 0; i < maxi; i++) {
    total += totalTime(maxj);
}

private int totalTime(int maxi) {
   int sum = 0;
    for(int i = 0; i < maxi; i++) {
        sum += a[i];
    }
    return sum;
}

и это более читаемо.

Ответ 5

foreach будет выполняться одинаково с for во всех сценариях [1], включая простые, такие как вы описываете.

Однако foreach имеет некоторые преимущества, не связанные с производительностью, над for:

  • Удобство. Вам не нужно поддерживать дополнительный локальный i вокруг (который не имеет никакой цели в жизни, кроме как облегчить цикл), и вам не нужно получать текущее значение в переменную самостоятельно; конструкция цикла уже позаботилась об этом.
  • Согласованность. С помощью foreach вы можете перебирать последовательности, которые не являются массивами с такой же легкостью. Если вы хотите использовать for для перебора упорядоченной последовательности, отличной от массива (например, карты/словаря), вам нужно написать код немного по-другому. foreach - то же самое во всех случаях, которые он охватывает.
  • Безопасность. С большой властью приходит большая ответственность. Зачем открывать возможности для ошибок, связанных с увеличением переменной цикла, если вам это не нужно в первую очередь?

Итак, как мы видим, foreach "лучше" использовать в большинстве ситуаций.

Тем не менее, если вам нужно значение i для других целей или если вы обрабатываете структуру данных, которая, как вам известно, является массивом (и существует фактическая конкретная причина, по которой она является массивом), увеличение функциональность, которая станет более доступной для приложений for.

[1] "Во всех сценариях" действительно означает "все сценарии, в которых коллекция удобна для повторения", что на самом деле было бы "большинством сценариев" (см. комментарии ниже). Я действительно думаю, что сценарий итерации, включающий итерационно-недружественную коллекцию, должен быть спроектирован, однако.

Ответ 6

Вероятно, вам следует рассмотреть также LINQ, если вы нацеливаете С# на язык, поскольку это еще один логический способ выполнения циклов.

Выполняя операцию над каждым элементом в списке, вы имеете в виду изменить его на месте в списке или просто что-то сделать с элементом (например, распечатать его, скопировать, изменить и т.д.)? Я подозреваю, что это последнее, так как foreach в С# не позволит вам изменять собранную вами коллекцию или, по крайней мере, не в удобном виде...

Вот две простые конструкции, сначала используя for, а затем используя foreach, которые посещают все строки в списке и превращают их в прописные строки:

List<string> list = ...;
List<string> uppercase = new List<string> ();
for (int i = 0; i < list.Count; i++)
{
    string name = list[i];
    uppercase.Add (name.ToUpper ());
}

(обратите внимание, что использование конечного условия i < list.Count вместо i < length с некоторой прекомпьютерной константой length считается хорошей практикой в ​​.NET, так как компилятору все равно придется проверять верхнюю границу, когда list[i] вызывается в цикле, если мое понимание верное, компилятор в некоторых случаях может оптимизировать проверку верхней границы, которую он обычно выполнял).

Вот эквивалент foreach:

List<string> list = ...;
List<string> uppercase = new List<string> ();
foreach (name in list)
{
    uppercase.Add (name.ToUpper ());
}

Примечание: в принципе конструкция foreach может перебирать любые IEnumerable или IEnumerable<T> в С#, а не только массивы или списки. Поэтому количество элементов в коллекции не может быть заранее известно или даже может быть бесконечным (в этом случае вам, конечно же, придется включить какое-то условие завершения в ваш цикл или оно не выйдет).

Вот несколько эквивалентных решений, о которых я могу думать, выраженный с помощью С# LINQ (и который вводит понятие лямбда-выражения, в основном встроенную функцию, принимающую x и возвращающую x.ToUpper () в следующих примерах):

List<string> list = ...;
List<string> uppercase = new List<string> ();
uppercase.AddRange (list.Select (x => x.ToUpper ()));

Или с uppercase списком, заполненным его конструктором:

List<string> list = ...;
List<string> uppercase = new List<string> (list.Select (x => x.ToUpper ()));

Или же с помощью функции ToList:

List<string> list = ...;
List<string> uppercase = list.Select (x => x.ToUpper ()).ToList ();

Или все же то же самое с типом вывода:

List<string> list = ...;
var uppercase = list.Select (x => x.ToUpper ()).ToList ();

или если вы не возражаете получить результат как IEnumerable<string> (перечислимая коллекция строк), вы можете удалить ToList:

List<string> list = ...;
var uppercase = list.Select (x => x.ToUpper ());

Или, возможно, другой с ключевыми словами from и select с С# SQL, которые полностью эквивалентны:

List<string> list = ...;
var uppercase = from name in list
                select name => name.ToUpper ();

LINQ очень выразителен и очень часто, я чувствую, что код более читабельен, чем простой цикл.

Второй вопрос, поиск элемента в списке и желание выйти, когда этот элемент найден, также может быть очень удобно реализован с использованием LINQ. Ниже приведен пример цикла foreach:

List<string> list = ...;
string result = null;
foreach (name in list)
{
    if (name.Contains ("Pierre"))
    {
        result = name;
        break;
    }
}

Вот простой эквивалент LINQ:

List<string> list = ...;
string result = list.Where (x => x.Contains ("Pierre")).FirstOrDefault ();

или с синтаксисом запроса:

List<string> list = ...;

var results = from name in list
              where name.Contains ("Pierre")
              select name;
string result = results.FirstOrDefault ();

Перечисление results выполняется только по требованию, что означает, что эффективно, список будет повторяться только до тех пор, пока условие не будет выполнено, при вызове метода FirstOrDefault на нем.

Надеюсь, это приведёт еще немного контекста к диску for или foreach, по крайней мере, в мире .NET.

Ответ 7

Как ответил Стюарт Голодец, это абстракция.

Если вы используете только i как индекс, в отличие от использования значения i для некоторых других целей, таких как

   String[] lines = getLines();
   for( int i = 0 ; i < 10 ; ++i ) {
      System.out.println( "line " + i + lines[i] ) ;
   }

тогда нет необходимости знать текущее значение i и возможность просто привести к возможности ошибок:

   Line[] pages = getPages();
   for( int i = 0 ; i < 10 ; ++i ) {
        for( int j = 0 ; j < 10 ; ++i ) 
             System.out.println( "page " + i + "line " + j + page[i].getLines()[j];
   }

Как говорит Андрей Кениг, "Абстракция - избирательное невежество"; если вам не нужно знать подробные сведения о том, как вы итерируете какую-либо коллекцию, а затем найдите способ игнорировать эти детали, и вы напишете более надежный код.

Ответ 8

Причины использования foreach:

  • Он предотвращает ползучесть ошибок (например, вы забыли i++ в цикле for), что может привести к сбою в работе цикла. Существует множество способов заглушить петли, но не так много способов заглушить петли foreach.
  • Он выглядит намного чище/менее загадочным.
  • В некоторых случаях цикл A for может быть даже невозможен (например, если у вас есть IEnumerable<T>, который не может быть проиндексирован как can IList<T>).

Причины использования for:

  • Эти типы циклов имеют небольшое преимущество в производительности при итерации по плоским спискам (массивы), потому что нет дополнительного уровня косвенности, созданного с помощью перечислителя. (Тем не менее, это увеличение производительности минимально.)
  • Объект, который вы хотите перечислить, не реализует IEnumerable<T> - foreach работает только с перечислениями.
  • Другие специализированные ситуации; например, если вы копируете из одного массива в другой, foreach не даст вам индексную переменную, которую вы можете использовать для адресации слота целевого массива. for - это единственное, что имеет смысл в таких случаях.

Два случая, которые вы перечисляете в своем вопросе, фактически одинаковы при использовании либо цикла - в первом, вы просто перебираете весь путь до конца списка, а во втором - break; после того, как вы нашли который вы ищете.

Чтобы просто объяснить foreach, этот цикл:

IEnumerable<Something> bar = ...;

foreach (var foo in bar) {
    // do stuff
}

- синтаксический сахар для:

IEnumerable<Something> bar = ...;

IEnumerator<Something> e = bar.GetEnumerator();
try {
    Something foo;
    while (e.MoveNext()) {
        foo = e.Current;
        // do stuff
    }
} finally {
    ((IDisposable)e).Dispose();
}

Ответ 9

Если вы выполняете итерацию по коллекции, которая реализует IEnumerable, более естественно использовать foreach, потому что следующий член в итерации назначается одновременно с завершением теста для достижения цели. Например.

 foreach (string day in week) {/* Do something with the day ... */}

является более простым, чем

for (int i = 0; i < week.Length; i++) { day = week[i]; /* Use day ... */ }

Вы также можете использовать цикл for в своей собственной реализации IEnumerable. Просто используйте для реализации GetEnumerator() ключевое слово С# yield в теле цикла:

yield return my_array[i];

Ответ 10

Java имеет оба типа циклов, на которые вы указали. Вы можете использовать любой из вариантов for loop в зависимости от ваших потребностей. Ваша потребность может быть такой

  • Вы хотите повторно запустить индекс вашего элемента поиска в списке.
  • Вы хотите получить элемент самостоятельно.

В первом случае вы должны использовать классический (c style) для цикла. но во втором случае вы должны использовать цикл foreach.

Цикл foreach также может использоваться в первом случае. но в этом случае вам нужно сохранить свой собственный индекс.

Ответ 11

Если вы можете сделать то, что вам нужно, foreach, тогда используйте его; если нет - например, если вам нужна указательная переменная по какой-либо причине, то используйте for. Простой!

(И ваши два сценария одинаково возможны с помощью for или foreach.)

Ответ 12

Я мог бы думать о нескольких причинах

  • you can't mess up indexes, также в мобильной среде, у вас нет оптимизаторов компилятора, и для написания петли, написанных для цикла, можно выполнить несколько проверок bounderay, где для каждого цикла выполняется только 1.
  • вы can't change data input size (добавьте/удалите элементы) во время повтора. Ваш код не так легко тормозит. Если вам нужно фильтровать или преобразовывать данные, используйте другие циклы.
  • вы можете выполнять итерацию по структурам данных, которые не могут быть доступны по индексу, но могут выполняться сканирование. Для каждого нужно только реализовать iterable interface (java) или расширить IEnumerable (С#).
  • у вас может быть меньше boiler plate, например, при разборе XML в нем разница между SAX и StAX, сначала нужна копия DOM в памяти, чтобы ссылаться на элемент, последний раз итерации по данным (это не так быстро, но это эффективная память)

Обратите внимание, что если вы ищете элемент в списке с каждым, вы, скорее всего, делаете это неправильно. Попытайтесь использовать hashmap или bimap, чтобы пропустить поиск вместе.

Предполагая, что программист хочет использовать for loop как for each с помощью итераторов, существует общая ошибка пропущенных элементов. Поэтому в этой сцене это более безопасно.

for ( Iterator<T> elements = input.iterator(); elements.hasNext(); ) {
   // Inside here, nothing stops programmer from calling `element.next();` 
   // more then once.
} 

Ответ 13

одна из причин не использовать foreach, по крайней мере, в java, заключается в том, что он создаст объект итератора, который в конечном итоге будет собран мусором. Таким образом, если вы пытаетесь написать код, который позволяет избежать сбора мусора, лучше избегать foreach. Тем не менее, я считаю, что это нормально для чистых массивов, потому что он не создает итератор.

Ответ 14

Похоже, что большинство предметов охвачены... Ниже приведены некоторые дополнительные заметки, которые я не вижу в отношении ваших конкретных вопросов. Это жесткие правила, в отличие от предпочтений стиля, так или иначе:

Я хочу выполнить операцию над каждым элементом в списке

В цикле foreach вы не можете изменить значение переменной итерации, поэтому, если вы хотите изменить значение определенного элемента в своем списке, вы должны использовать for.

Также стоит отметить, что "классный" способ теперь использовать LINQ; есть много ресурсов, которые вы можете найти, если вы заинтересованы.

Ответ 15

Говоря о чистом коде, оператор foreach гораздо быстрее читает, чем оператор for!

Linq (в С#) может сделать то же самое, но начинающим разработчикам, как правило, нелегко их читать!

Ответ 16

foreach на порядок медленнее для реализации тяжелой коллекции.

У меня есть доказательство. Это мои выводы

Я использовал следующий простой профайлер, чтобы проверить их производительность

static void Main(string[] args)
{
    DateTime start = DateTime.Now;
    List<string> names = new List<string>();
    Enumerable.Range(1, 1000).ToList().ForEach(c => names.Add("Name  = " + c.ToString()));
    for (int i = 0; i < 100; i++)
    {
        //For the for loop. Uncomment the other when you want to profile foreach loop
        //and comment this one
        //for (int j = 0; j < names.Count; j++)
          //  Console.WriteLine(names[j]);
        //for the foreach loop
        foreach (string n in names)
        {
            Console.WriteLine(n);
        }
    }
    DateTime end = DateTime.Now;
    Console.WriteLine("Time taken = " + end.Subtract(start).TotalMilliseconds + " milli seconds");

И я получил следующие результаты

Время принятое = 11320,73 миллисекунды (для цикла)

Время принятое = 11742.3296 миллисекунд (цикл foreach)

Ответ 17

A foreach также уведомляет вас, если коллекция, которую вы перечисляете с помощью изменений (т.е. у вас есть 7 элементов в вашей коллекции... пока другая операция в отдельном потоке не удалена, и теперь у вас есть только 6 @_ @)

Ответ 18

Просто хотел добавить, что тот, кто думает, что foreach переводится на и, следовательно, не имеет разницы в производительности, ошибочен. Есть много вещей, которые происходят под капотом, т.е. Перечисление типа объекта, которое НЕ является простым циклом цикла. Он больше похож на цикл итератора:

Iterator iter = o.getIterator();
while (iter.hasNext()){
     obj value = iter.next();
     ...do something
}

который существенно отличается от простого цикла. Если вы не понимаете, почему, то найдите vtables. Кроме того, кто знает, что в функции hasNext? Насколько нам известно, это может быть:

while (!haveiwastedtheprogramstimeenough){
}
now advance

Отвлечение в сторону, есть функция неизвестной реализации и эффективности. Поскольку компиляторы не оптимизируют границы функций, здесь нет оптимизации, просто ваш простой просмотр и вызов функции vtable. Это не просто теория, на практике я видел значительные ускорения, переключаясь с foreach на стандартный С# ArrayList. Справедливости ради следует сказать, что это был аррайалист с примерно 30 000 предметов, но все же.