Введённая в С++11 форма цикла: Оператор for с диапазоном. The range-based for statement. Знакомство

  • Для понимания этой темы нужно знать саму суть нужности циклов и иметь небольшое представление о механизме процесса выполнения циклов. Опыт решения учебных задач на массивы поможет уяснить тему ещё быстрее.
В современных компиляторах С++ семейство циклов дополнено циклом обхода диапазона. Это такой вид цикла, который умеет обходить массивы, std::vector и std::array. Если у вас компилятор или старый, или устаревающий, то эта новая разновидность циклов у вас работать не будет.

  • Цикл, основанный на диапазоне, был добавлен в С++11

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

В коде листинга #2 задействуется непосредственно сама форма описываемого цикла, и арифметика указателей. В обычной форме цикла for это можно воспроизвести так:

N — 1 здесь только потому что отсчёт у С++ происходит с нуля и отчего последний элемент массива будет arr[N-1]. От последнего адреса элемента массива отнимаем текущее значение счётчика и попадаем в нужную ячейку обращённого массива. Маленький математический трюк и арифметика указателей, никакого мошенничества.
Для обхода массива в обратном порядке, в общем, описываемый в статье цикл не применяют обычно.
Цикл, основанный на диапазоне, имеет некоторого рода защиту от изменений обходимого своего элемента:

При обходе массива циклом, написанным в листинге #4, компилятор создаёт новые, невидимые для нас, переменные, в которые копирует значения из ячеек массива. Именно те клоны изменяют свои значения, а доступа к ним вне непосредственно цикла у нас вообще нет. Поэтому возможное ожидание увидеть модифицированный массив не оправдывается.
С целью избежания создания клонов нужно применить немного другой подход: использовать &.

В идеале, чтобы защищать элементы массива от изменений и программа не тратила время на взращение клонированных значений, использовать спецификатор const, т. е. обозначать, что элементы массива изменяться не должны, в сочетании с &. В таком случае и элементы массива не будут изменяться (программа просто не скомпилируется, если в коде написать что-то изменяющее непосредственную ячейку массива) и скорость работы цикла возрастёт, потому что создание клонов требует времени, а коли клоны не создаются, то и заниматься можно более нужным. Поэтому в листинге #1 мной используется константное обозначение значения из проходимой циклом ячейки:

Смысл использования описываемой в статье формы цикла в том, что его очень удобно использовать для сущностей, тип у которых смотрится в написании трудночитаемо и достаточно страшно. Поскольку спецификатор автоматического вывода типа auto, введённый в С++11, автоматически выводит тип из переменных, а компилятор знает, где заканчивается диапазон, у нас есть всё, чтобы обойти каждое значение из диапазона и обработать его в правилах с соответствием присущего обходимому значению типа. Благодаря этому код выглядит заметно симпатичнее глазу. Когда у вас пойдут типы чуть сложнее чем массив и применение шаблонов, тогда вы сможете ощутить непосредственное удобство прямого обхода массива с помощью описанной формы цикла.

11 комментариев на «“Введённая в С++11 форма цикла: Оператор for с диапазоном. The range-based for statement. Знакомство”»

  1. Igor:

    почему я создаю массив на 20 элемов, задаю 5, а выводит 5 + нули? про какую границу знает компилятор?

    • Массив в 20 элементов, вот все 20 и обходит.

  2. Igor:

    а про какую границу идет речь? про конец массива? так об этом знает всё, что есть в С++

    • Я не знаю, чего Вы сейчас хотите.
      Раньше было возможно 2 цикла: while, for и всякие разные способы, а теперь добавился for с диапазоном.
      Если массив статический, то диапазон обхода от начала массива до последнего элемента массива.
      Если массив динамический, то диапазон обхода от начала массива до последнего элемента согласно размеру size, но не по всему возможному резерву.

      Но я не знаю, чего Вы хотите, поэтому затрудняюсь давать ответ в принципе.

    • Это всего лишь самый короткий синтаксис обхода всего массива.
      Напишите код обхода массива не указывая границу (ни последний элемент, ни адрес последнего элемента массива, ни количество ячеек заданных массиву) явным образом.

  3. Igor:

    while (mas[f]){f++;} — цикл будет накручиваться пока не закончатся заданные элементы в массиве, и не будет накручиваться до конца массива. Например: n=20; mas[n]={1,2,3,4,5} после написанного цикла f=5, а не 20. А при for ( auto i:mas){i++;}        i=20 а не 5

  4. Igor:

    я не думал про этот подвох. получается нельзя, что бы в массив записывался ноль самостоятельно?

    • Это не подвох. Можно, но не в общем случае. Если 0 служит в качестве признака-окончания ввода, то без проблем, но если 0 не может служить в качестве окончания ввода, по причине возможного использования, то нельзя использовать такой вариант написания кода.

      В круглых скобках while выражение, отдающее булевую истинну или ложь. Поскольку ноль всегда трактуется как ложь (false), то условие Вашего кода будет считаться выполненным при первом встреченном выражении, которое равносильно false. Встретили ноль, истрактовали его как false (ноль всегда false) и посчитали, что условие цикла выполнено.

      Массив определён границами, которые нарушать нельзя. Если массиву сказали, что у массива максимум 20 элементов, то С++ выделяет в памяти ячейки на 20 элементов. Начало известно всегда, а вот где конец, обычно нужно или высчитывать или обозначать напрямую. Если выйти за допустимую норму, т. е., например, попробовать записать в 21-ю ячейку значение, то произойдёт нарушение границы массива, это повлечёт за собой UB. Память выделена для 20 ячеек, но не выделялась для 21-й. В невыделенное пространство в памяти производить запись незаконно.
      Приведённый Вами пример UB. То, что он сработал правильно, это только стечение обстоятельства. Просто так получилось, что самый близкий сосед к последней ячейке массива (21-я ячейка) оказался со значением 0. При ином запуске программы всё может измениться. Вот эта последняя ячейка массива справа (сейчас 20-я) — это и есть граница массива, о которой спрашивалось Вами. Не последний записанный в массив элемент, а край массива, выходить за который незаконно.

    • Поправка. В том виде, какой есть, поведение не UB, потому что всем оставшимся элементам массива автоматически приписалось значение нуля. Но поведение свалится в UB как только весь массив наполнится. Если в значениях массива не окажется ноля, то цикл пойдёт за массив и будет продолжаться пока ноль не найдётся, а где ноль найдётся в непринадлежащей программе памяти, зависит от воли случая.

  5. Igor:

    Спасибо, теперь немного разобрался

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

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

Поиск

 
     

Случайная книга в электронном формате

https://www.litres.ru/ildar-habibullin/samouchitel-java/?lfrom=15589587
Яндекс.Метрика