- Что такое циклы
- Зачем нужны циклы
- Языки программирования с циклами
- Типы циклов
- For
- For-each
- While
- Do-While
- Вложенные циклы
- Repeat / until
- Операторы циклов
- Break
- Continue
- Частые ошибки при работе с циклами
Что такое циклы
Цикл в программировании — это фундаментальная управляющая конструкция, которая позволяет выполнять определенный блок кода — тело цикла — несколько раз. Такие конструкции встречаются в большей части популярных языков программирования (хоть и не во всех из них рекомендованы к использованию) и применяются довольно часто, так как они позволяют автоматизировать выполнение повторяющихся операций, а также писать компактные и понятные программы. Простой пример: если необходимо вывести на экран числа от 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);
}
Эта программа выведет на экран цифры от 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);
}
For-each
For-each тоже используется для перебора элементов массивов и коллекций. Конструкция имеет следующий вид, который может отличаться в зависимости от языка программирования:
foreach (тип переменная in имя коллекции)
{
тело/подпрограмма
}
Итак, после ключевого слова foreach в скобках указываются:
- тип элементов коллекции, например, int;
- переменная, которая будет принимать значение текущего элемента;
- имя коллекции, которую нужно перебрать.
Далее идет подпрограмма, которую нужно выполнить.
Пример на C# с уже заполненными значениями:
List<int> numbers = new() { 0, 1, 2, 3 };
foreach (int element in numbers)
{
Console.Write($"{element} ");
}
Также существует функция высшего порядка forEach(), которая тоже производит какие-то действия над каждым элементом, но она не представляет из себя цикл.
While
Циклы while с предусловием используется в ситуациях, когда количество итераций неизвестно заранее. Цикл с предусловием может повторяться сколько угодно раз: от нуля до бесконечности, так как условие прописывается перед телом, а в дальнейшем — проверяется перед началом каждой итерации, и, пока оно верно, цикл будет продолжаться. Пример использования такой управляющей конструкции на JS:
let i = 0;
while (i < 5) {
console.log(i);
i += 1;
}
Дословно можно прочитать так: «пока i меньше пяти, делай то, что указано в теле».
Теперь рассмотрим этот код более подробно:
- «let i = 0» — это объявление переменной и ее инициализация значением 0;
- «i < 5» — это постановка условия, которое проверяется перед выполнением тела, здесь может быть любое логическое выражение;
- «console.log(i)» — вывод значения переменной;
- «i += 1» — указываем, что счетчик должен увеличиться на единицу.
Иногда while с предусловием используют вместо for, но это не самая лучшая практика, так как цикл с предусловием — это совершенно иная по смыслу конструкция.
Do-While
Do-while, или while с постусловием — это почти то же самое, что while с предусловием. Разница в том, что при использовании while с постусловиемусловие прописывается после тела, а также уже при выполнении программы оно проверяется после выполнения тела. Это значит, что тело выполнится как минимум единожды, даже если условие ложно. И именно в таких случаях чаще всего используется while с постусловием — когда нужно, чтобы подпрограмма выполнилась хотя бы один раз. Схема такой конструкции выглядит следующим образом:
do {
тело/подпрограмма
}
while (условие);
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
// И так далее
На Python реализация выглядит схожим образом:
for i in range(1, 11):
for j in range(1, 11):
print(f"{i} * {j} = {i * j}")
Такие конструкции лучше использовать только тогда, когда других способов реализовать нужную программу нет. Во-первых, вложенные структуры сильно усложняют код. Во-вторых, они могут снижать производительность, то есть, повышать время выполнения программы. Так происходит из-за того, что количество итераций равно произведению количества выполнений внешней и вложенной части. В примере про таблицу умножения это произведение равно 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.
Эта программа выведет на экран цифры от 0 до 4, так как на цифре 5 условие будет истинно. Эквивалентный код на JavaScript:
let i = 0;
do {
console.log(i);
i += 1;
} while (i < 5);
Команды 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);
}
Когда при переборе массива будет найдено число 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;
}
Этот вариант кода делает то же самое, что предыдущий, но при этом не вызывает проблем, которые могут возникнуть при использовании 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]
Здесь реализовано следующее поведение: каждый раз, когда программа встречает четное число, данная итерация прерывается и начинается новая. Но оператор 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]
Результат выполнения этих двух программ будет абсолютно одинаковым, но второй вариант гораздо безопаснее и даже чуть-чуть проще.
На практике же почти не существует случаев, когда код с применением операторов 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);
}
- Использование неподходящего типа управляющей конструкции. Такие ошибки могут не привести к некорректному исполнению программы или неожиданному поведению, но с большой долей вероятности сделают код менее понятным для других разработчиков и внесут в него лишние сложности. Чаще всего причинами таких ошибок являются недостаточный опыт программиста и неполное понимание поставленной задачи. Допустим, необходимо вывести на экран элементы массива (количество итераций известно заранее):
// Использование 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]);
Выход за пределы границ коллекции или массива. Скорее всего, это приведет к ошибке в работе программы. Чаще всего программисты выходят за границы коллекций и массивов из-за ошибок в вычислении их длины или вовсе неправильного понимания концепции индексов. Пример такого кода:
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)
Чтобы избежать этой ошибки при переборе коллекций и массивов, лучше прибегать к командам for-of, for-in, for-each.
- Отсутствие инициализации переменной. Причина возникновения таких ошибок — банальная невнимательность.