Включите исполнение JavaScript в браузере, чтобы запустить приложение.
24 окт 2024

Типы циклов в программировании

Подробное руководство по циклам в программировании для новичков. Что такое цикл и зачем они нужны. Разберем основные типы циклов: For, Foreach, While, Do While, Вложенные циклы. Частые ошибки при работе с циклами. Когда нужно применять циклы и какой именно.
  1. Что такое циклы
  2. Зачем нужны циклы
  3. Языки программирования с циклами
  4. Типы циклов
  5. For
  6. For-each
  7. While
  8. Do-While
  9. Вложенные циклы
  10. Repeat / until
  11. Операторы циклов
  12. Break
  13. Continue
  14. Частые ошибки при работе с циклами

Что такое циклы

Цикл в программировании — это фундаментальная управляющая конструкция, которая позволяет выполнять определенный блок кода — тело цикла — несколько раз. Такие конструкции встречаются в большей части популярных языков программирования (хоть и не во всех из них рекомендованы к использованию) и применяются довольно часто, так как они позволяют автоматизировать выполнение повторяющихся операций, а также писать компактные и понятные программы. Простой пример: если необходимо вывести на экран числа от 1 до 100, то без циклов пришлось бы писать 100 строк кода — а с ними задача сводится к написанию всего нескольких.

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

Существуют основные команды, с помощью которых вводят такие управляющие конструкции — это while, for и repeat. Все они имеют вариации: бывает while с предусловием и постусловием.

Зачем нужны циклы

Циклы — это очень часто используемая управляющая конструкция в программировании и информатике. Причина такой распространенности заключается в том, что они позволяют выполнять необходимые действия многократно, то есть, автоматизировать выполнение этих действий, и при этом без надобности переписывать один и тот же код. Что еще позволяют делать такие управляющие конструкции:

  • писать компактный, легко читаемый и поддерживаемый код;
  • эффективно обрабатывать и изменять большие объемы данных: массивы, списки, словари и другие, например, искать сумму всех элементов массива или отображать его элементы построчно;
  • перебирать элементы коллекций.

Языки программирования с циклами

Циклы используются практически везде, но есть нюансы. Где-то их не рекомендуется использовать, а где-то присутствует лишь некоторые команды. Рассмотрим самые популярные языки программирования и особенности управляющих конструкций в них.

  • C и C++ — применяются для разработки операционных систем, игр и приложений. Используются конструкции for, while, do-while (в C++ и C# к этим конструкциям добавляется for-each).
  • Python — популярен в областях разработки программного обеспечения, анализа данных, машинного обучения, ИИ. Поддерживаются команды for и while.
  • Java — применяется для разработки различных приложений. Можно использовать команды for, for-each, while, do-while.
  • JavaScript — используется в сфере веб-разработки. Поддерживаются следующие команды: for, for-of, for-in, while, do-while. В JavaScript хорошей практикой считают использование функций высшего порядка, а прибегать к циклам следует только в ситуациях, когда без них не обойтись.
  • PHP — активно используется при работе с базами данных и разработке серверной части веб-приложений. Поддерживаются конструкции for, for-each, while, do-while.

Типы циклов

For

For — это довольно распространенный и универсальный тип циклов. Применяется он тогда, когда количество итераций известно заранее. Простой пример управляющей конструкции с использованием for на JavaScript:

for (let i = 0; i < 5; i += 1) {
  console.log(i);
}
javascript

Эта программа выведет на экран цифры от 0 до 4. Теперь рассмотрим каждую запись в этом коде по отдельности:

  • «let i = 0;» — здесь происходит объявление переменной, которая будет изменяться во время каждой итерации. По-другому это называется инициализацией: в данном случае переменная i инициализируется значением 0. То есть, 0 — это начальное значение счетчика. Инициализация выполняется один раз, перед началом исполнения управляющей конструкции;
  • «i < 5;» — это условие, которое проверяется перед каждой итерацией. Пока оно истинно, цикл продолжает повторяться. В данном случае фиксируется, что переменная i должна быть строго меньше пяти;
  • «i += 1» — это действие, которое должно выполниться после каждой итерации. В данном случае значение счетчика должно увеличиться на единицу;
  • «console.log(i);» — это тело цикла, которое иначе иногда называют подпрограммой. Здесь может быть почти любой код.

Также в JavaScript выделяют команды for-in и for-of, они используются для перебора элементов различных структур данных. For-in нужен для перебора свойств объектов, а for-of — для перебора элементов массива. Выглядит это так:

const arr = [10, 20, 30, 40, 50];
 
for (const item of arr) {
  console.log(item);
}
javascript

For-each

For-each тоже используется для перебора элементов массивов и коллекций. Конструкция имеет следующий вид, который может отличаться в зависимости от языка программирования:

foreach (тип переменная in имя коллекции)
{
  тело/подпрограмма
}
javascript

Итак, после ключевого слова foreach в скобках указываются: 

  • тип элементов коллекции, например, int;
  • переменная, которая будет принимать значение текущего элемента;
  • имя коллекции, которую нужно перебрать.

Далее идет подпрограмма, которую нужно выполнить.

Пример на C# с уже заполненными значениями:

List<int> numbers = new() { 0, 1, 2, 3 };
foreach (int element in numbers)
{
	Console.Write($"{element} ");
}
c#

Также существует функция высшего порядка forEach(), которая тоже производит какие-то действия над каждым элементом, но она не представляет из себя цикл.

While

Циклы while с предусловием используется в ситуациях, когда количество итераций неизвестно заранее. Цикл с предусловием может повторяться сколько угодно раз: от нуля до бесконечности, так как условие прописывается перед телом, а в дальнейшем — проверяется перед началом каждой итерации, и, пока оно верно, цикл будет продолжаться. Пример использования такой управляющей конструкции на JS:

let i = 0; 


while (i < 5) { 
  console.log(i); 
  i += 1; 
}
javascript

Дословно можно прочитать так: «пока i меньше пяти, делай то, что указано в теле».

Теперь рассмотрим этот код более подробно:

  • «let i = 0» — это объявление переменной и ее инициализация значением 0;
  • «i < 5» — это постановка условия, которое проверяется перед выполнением тела, здесь может быть любое логическое выражение;
  • «console.log(i)» — вывод значения переменной;
  • «i += 1» — указываем, что счетчик должен увеличиться на единицу.

Иногда while с предусловием используют вместо for, но это не самая лучшая практика, так как цикл с предусловием — это совершенно иная по смыслу конструкция.

Do-While

Do-while, или while с постусловием — это почти то же самое, что while с предусловием. Разница в том, что при использовании while с постусловиемусловие прописывается после тела, а также уже при выполнении программы оно проверяется после выполнения тела. Это значит, что тело выполнится как минимум единожды, даже если условие ложно. И именно в таких случаях чаще всего используется while с постусловием — когда нужно, чтобы подпрограмма выполнилась хотя бы один раз. Схема такой конструкции выглядит следующим образом:

do {
  тело/подпрограмма
} 
while (условие);
javascript

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

Вложенные циклы

Вложенные циклы — это циклы, которые вкладываются в другие циклы. Чаще всего для их создания используется команда for. Применяются такие конструкции для работы со сложными, многомерными структурами данных: матрицы, таблицы.

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

// Внешняя часть
for (let i = 1; i <= 10;  i += 1) { 
// Внутренняя/вложенная часть
  for (let j = 1; j <= 10;  i += 1) { 
// Тело 
    console.log(`${i} * ${j} = ${i * j}`); 
  }
}
// Вывод:
// 1 * 1 = 1
// 1 * 2 = 2
// 1 * 3 = 3
// 1 * 4 = 4
// 1 * 5 = 5
// 1 * 6 = 6
// 1 * 7 = 7
// 1 * 8 = 8
// 1 * 9 = 9
// 1 * 10 = 10
// И так далее
javascript

На Python реализация выглядит схожим образом:

for i in range(1, 11):  
    for j in range(1, 11):  
        print(f"{i} * {j} = {i * j}")  
py

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

Repeat/until

Repeat/until можно назвать альтернативой while с постусловием. Тело такой управляющей конструкции тоже выполняется как минимум один раз, но существенное отличие заключается в том, что завершение цикла repeat/until происходит тогда, когда заданное условие становится истинным, а не ложным, как в случае с while. Пример такой конструкции на Pascal:

var
  i: Integer;
begin
  i := 0; 
 
  repeat
	WriteLn(i); 
	i := i + 1; 
  until i >= 5; 
end.
git

Эта программа выведет на экран цифры от 0 до 4, так как на цифре 5 условие будет истинно. Эквивалентный код на JavaScript:

let i = 0;


do {
  console.log(i);
  i += 1;
} while (i < 5);
javascript

Команды repeat/until не существует в большинстве языков программирования.

Операторы циклов 

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

Break

Оператор break используется для выхода из цикла: как только программа встречает этот оператор, происходит немедленное завершение цикла. После этого начинает выполняться код, который следует за данным циклом.

К примеру, нужно закончить цикл, как только в массиве будет найден определенный элемент, тогда можно написать следующий код: 

const numbers = [10, 20, 30, 40, 50, 60, 70];
 
for (const num of numbers) {
  if (num === 50) {
    break;
  }
  console.log(num);
}
javascript

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

Код, указанный выше можно легко заменить с помощью while с предусловием:

const numbers = [10, 20, 30, 40, 50, 60, 70];
 
let i = 0;
while (numbers[i] !== 50) {
  console.log(numbers[i]);
  i += 1;
}
javascript

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

Continue

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

Например, необходимо добавлять в результирующий массив только нечетные цифры из уже существующего массива numbers:

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const result = [];
 
for (const item of numbers) {
  if (item % 2 === 0) {
    continue;
  }
  result.push(item);
}


console.log(result);
// [1, 3, 5, 7, 9]
javascript

Здесь реализовано следующее поведение: каждый раз, когда программа встречает четное число, данная итерация прерывается и начинается новая. Но оператор continue также не рекомендован к использованию в большинстве случаев. Причины те же, по которым стоит избегать использования break: поломка логики и трудности с отладкой.

Предыдущий код также можно легко заменить на другой, и тоже с использованием условной конструкции:

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const result = [];
 
for (const item of numbers) {
  if (item % 2 !== 0) {
    result.push(item);
  }
}


console.log(result);
// [1, 3, 5, 7, 9]
javascript

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

На практике же почти не существует случаев, когда код с применением операторов break и continue нельзя заменить на эквивалентные варианты программы, поэтому их использование довольно редко можно назвать оправданным.

Частые ошибки при работе с циклами

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

  • Бесконечный цикл — класс циклов, которые никогда не заканчиваются. Выделяют две причины такого поведения: либо условие является всегда истинным, либо изменение переменной происходит некорректно / не происходит вообще. Примеры такого кода:
// Такое условие всегда будет оставаться истинным
for (let i = 1; ; i += 1) {
  console.log(i);
}


// И такое тоже
for (let i = 0; i < Infinity; i += 1) {
  console.log(i);
}


// Здесь значение счетчика не изменяется
while (i < 10) {
  console.log(i);
}
javascript
  • Использование неподходящего типа управляющей конструкции. Такие ошибки могут не привести к некорректному исполнению программы или неожиданному поведению, но с большой долей вероятности сделают код менее понятным для других разработчиков и внесут в него лишние сложности. Чаще всего причинами таких ошибок являются недостаточный опыт программиста и неполное понимание поставленной задачи. Допустим, необходимо вывести на экран элементы массива (количество итераций известно заранее):
// Использование while с предусловием в данном случае нежелательно, такой код менее читаемый, 
// условия разбросаны,  есть вероятность ошибок 
const numbers = [10, 20, 30, 40, 50];
let i = 0;
 
while (i < numbers.length) {
  console.log(numbers[i]);
  i += 1;
}


// В данном случае семантически больше подходит for, также код становится более читаемым и понятным
const numbers = [10, 20, 30, 40, 50];
 
for (let i = 0; i < numbers.length; i += 1) {
  console.log(numbers[i]);
javascript

Выход за пределы границ коллекции или массива. Скорее всего, это приведет к ошибке в работе программы. Чаще всего программисты выходят за границы коллекций и массивов из-за ошибок в вычислении их длины или вовсе неправильного понимания концепции индексов. Пример такого кода:

const arr = [1, 2, 3, 4, 5];
for (let i = 0; i <= arr.length; i += 1) {
  console.log(arr[i]);  
}
// В данном случае длина массива равна пяти, а индекс последнего элемента — четырем
// Поэтому i в условии должно быть строго меньше длины массива (let i = 0; i < arr.length; i += 1)
javascript

Чтобы избежать этой ошибки при переборе коллекций и массивов, лучше прибегать к командам for-of, for-in, for-each.

  • Отсутствие инициализации переменной. Причина возникновения таких ошибок — банальная невнимательность.