Урок №91. Цикл foreach

  Юрий  | 

  |

  Обновл. 4 Сен 2021  | 

 144941

 ǀ   27 

На этом уроке мы рассмотрим использование цикла foreach в языке С++.

Цикл foreach

На уроке №76 мы рассматривали примеры использования цикла for для осуществления итерации по каждому элементу массива. Например:

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

Поэтому в C++11 добавили новый тип цикла — foreach (или «цикл, основанный на диапазоне»), который предоставляет более простой и безопасный способ итерации по массиву (или по любой другой структуре типа списка).

Синтаксис цикла foreach следующий:

for (объявление_элемента : массив)
   стейтмент;

Выполняется итерация по каждому элементу массива, присваивая значение текущего элемента массива переменной, объявленной как элемент (объявление_элемента). В целях улучшения производительности объявляемый элемент должен быть того же типа, что и элементы массива, иначе произойдет неявное преобразование. Рассмотрим простой пример использования цикла foreach для вывода всех элементов массива math:

Результат выполнения программы:

0 1 4 5 7 8 10 12 15 17 30 41

Рассмотрим детально, как это всё работает. При выполнении цикла foreach переменной number присваивается значение первого элемента (т.е. значение 0). Дальше программа выполняет стейтмент вывода значения переменной number, т.е. нуля. Затем цикл выполняется снова, и значением переменной number уже является 1 (второй элемент массива). Вывод значения number выполняется снова. Цикл продолжает свое выполнение до тех пор, пока в массиве не останется непройденных элементов. В конце выполнения программа возвращает 0 обратно в операционную систему с помощью оператора return.

Обратите внимание, переменная number не является индексом массива. Ей просто присваивается значение элемента массива в текущей итерации цикла.

Цикл foreach и ключевое слово auto


Поскольку объявляемый элемент цикла foreach должен быть того же типа, что и элементы массива, то это идеальный случай для использования ключевого слова auto, когда мы позволяем C++ вычислить тип данных элементов массива вместо нас. Например:

Цикл foreach и ссылки

В примерах, приведенных выше, объявляемый элемент всегда является переменной:

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

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

Конечно же, хорошей идеей будет сделать объявляемый элемент константным, тогда вы сможете его использовать в режиме «только для чтения»:

Правило: Используйте обычные ссылки или константные ссылки в качестве объявляемого элемента в цикле foreach (в целях улучшения производительности).

Еще один пример


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

Обратите внимание, здесь нам уже не нужно вручную прописывать индексацию массива. Мы можем получить доступ к каждому элементу массива непосредственно через переменную score.

Цикл foreach и не массивы

Циклы foreach работают не только с фиксированными массивами, но также и со многими другими структурами типа списка такими, как векторы (например, std::vector), связанные списки, деревья. Не беспокойтесь, если вы не знаете, что это такое (мы всё это рассмотрим чуть позже). Просто помните, что циклы foreach обеспечивают гибкий и удобный способ итерации не только по массивам:

Цикл foreach не работает с указателями на массив


Для итерации по массиву, цикл foreach должен знать длину массива. Поскольку массивы, которые распадаются в указатель, не знают своей длины, то циклы foreach с ними работать не могут!

По этой же причине циклы foreach не работают с динамическими массивами.

Могу ли я получить индекс текущего элемента?

Циклы foreach не предоставляют прямой способ получения индекса текущего элемента массива. Это связано с тем, что большинство структур, с которыми могут использоваться циклы foreach (например, связанные списки), напрямую не индексируются!

Заключение

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

Тест

Это должно быть легко!

Объявите фиксированный массив со следующими именами: Sasha, Ivan, John, Orlando, Leonardo, Nina, Anton и Molly. Попросите пользователя ввести имя. Используйте цикл foreach для проверки того, не находится ли имя, введенное пользователем, уже в массиве.

Пример результата выполнения программы:

Enter a name: Sasha
Sasha was found.

Enter a name: Masha
Masha was not found.

Подсказка: Используйте std::string в качестве типа массива.

Ответ

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (370 оценок, среднее: 4,93 из 5)
Загрузка...

Комментариев: 27

  1. Владимир:

    Привет, благодарю за очередной урок. Сделал через getline, через функцию не вышло передать массив, пришлось в мейне всё закидывать.

  2. Ульяна:

    Не пишу лишнее, только цикл.

    1. Александр:

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

      PS: При вашей реализации лучше, скорее всего, писать не was/wasn't found, а что-то на подобие does/doesn't equal.

  3. Артурка:

    Разве не правильние говорить не foreach цикл, а range based for? Так как в STL хедере <algorithm> есть функция for_each(InputIt first, InputIt last, UnaryFunction f) и далее может возникнуть путаница в терминах при изучении.

    1. Фото аватара Юрий:

      Хороший вопрос.

      Дело в том, что есть уже устоявшиеся «народные» термины, «цикл foreach» относится к таковым. Да, «foreach» не является ключевым словом, и при использовании в коде мы нигде не указываем «foreach». Но это «народное название», которое позволяет сразу понять, о чем идет речь.

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

      То, что есть функция for_each() — это ничего (по большей мере) не значит. Повторюсь, ключевого слова "foreach" нет, следовательно и спутать конкретную функцию for_each() с "народным" названием цикла трудно, т.к. в коде цикл foreach объявляется с помощью ключевого слова for. А в устной речи мы всегда конкретизируем, говорим ли мы о цикле, или о функции.

  4. Andrey:

    Можно провести итерацию , если только переданный массив скопировать в другой массив в функции

    1. Andrii:

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

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

      Автор статьи явно не указал данную «фичу», так как она не целесобразна.

  5. Vlad:

  6. Onium:

    1. VEX:

      Неправильно, у тебя сравнивается ввод с элементов массива "Sasha"и если ввод совпадает с "Sasha", то он выводит, что нашёл, и завершает цикл, а в противном случае он выводит, что не нашёл и выходит из цикла, а остальные элементы не обрабатываются

  7. Кетчуп:

    Допустим что в цикле foreach есть массив numbers и элемент number.
    Если вспомнить что под капотом ссылок указатели и ссылка это разыменованный указатель, то легко понять что индекс элемента будет равен &number — numbers, потому что numbers + n = &(numbers[n]).
    (https://ravesli.com/urok-83-adresnaya-arifmetika-i-indeksatsiya-massiva/)

    1. Viktor:

      Не очень с тобой соглашусь по двум пунктам.
      1) как я читал на многих форумах не регламентируется, то как ссылка должна быть реализована внутри. К сожалению, пока не нашел способ почитать сам стандарт без покупки за большую сумму.
      2) массив не обязан хранить элементы, совпадающие с индексом. Кроме этого, элементы массива могут быть вообще не int типа

  8. Bampi:

    Куда более интересный вопрос: откуда for( auto& ind : arr) знает размер массива?

    Если правильно все понял, то при объявлении масства, например:

    имеет представление int[10] и из

    Откуда for знает, что массив состоит из 10 элементов. Или как-то по другому?

  9. Павел:

    Блин думал над таким же решением как в ответе, но подумал нафиг плодить сущности))), так что мой вариант решения)))

  10. Константин:

    И правда не сложно!

  11. Евгений:

    Решил потренироваться с динамической памятью, чтобы потом указать размер фиксированного массива. Но я так понял у меня это не выйдет?
    И мне нужно вместо этого создать динамический массив с таким размером?

    1. Павел:

      Размер статического массива должен быть известен во время компиляции. Динамически выделенная переменная  запрашивает  память во время работы программы. Поэтому компилятор не разрешит так задавать длину.

      Динамическое выделение массива позволяет нам устанавливать его длину во время выполнения программы:

  12. Nikita:

    Почему у всех в комментариях есть переменная bool? Я что-то пропустил?

    1. Алексей:

      Написать можно как угодно, это работает.
      Но Вы не написали, что берете const auto &array, в серьезных программах может быть беда.

      Второе — не написано return 0 в конце main.

      Сам снова тупанул написав в цикл проверку без прерывания.

  13. Алексей:

    Тест простой, правда тупанул насчет проверки, зачем-то влепил в цикл.
    Хотел сделать проверку на ввод — не работает. Ту же пустую строку.

    1. Павел:

      Не понял

      Поправьте если я что то не так понял, вот это "if (name == "\n")" означает условие начала с новой строки, или если человек нажал ENTER? Потом вроде всё понятно зачищаем всё что можно в том числе и данные с экрана. Затем новый непонятный стэймент "while (check)" это что?

      Объясните плиз, а то не понимаю(((

  14. Анастасия:

    Я использую getline, чтобы записать введённую строку в переменную. Вопрос:
    почему если я перед этим очищаю cin с помощью

    то у меня после ввода имени идёт ещё одна, пустая строка и потом, соответственно, пишет, что такого имени нет. Без cin.ignore(1000, '\n'); всё находит нормально и пустой строки нет.
    Вот мой код:

    1. Михаил:

      Ты этим не очищаешь cin, а просишь ввести строку, которая после ввода будет просто отброшена. И поэтому первая строка, что ты вводишь отбрасывается, а вторая уже записывается в переменную.

      1. Анастасия:

        перечитала объяснение про cin.ignore, разобралась, спасибо!

  15. Вячеслав:

    у меня так получилось

  16. Павел:

    У меня решение в тексте выдает сразу "Enter a name: was not found."

  17. Игорь:

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *