7.9 – Инструкции for
Безусловно, наиболее часто используемой инструкцией цикла в C++ является оператор for
. Оператор for
(также называемый циклом for
) предпочтителен, когда у нас есть очевидная переменная цикла, потому что он позволяет нам легко и кратко определять, инициализировать, тестировать и изменять значение переменных цикла.
Начиная с C++11, существует два разных типа циклов for
. В этом уроке мы рассмотрим классический оператор for
, а новый оператор for
на основе диапазона в следующем уроке (10.19 – Циклы for
-each), как только рассмотрим некоторые другие необходимые темы, такие как массивы и итераторы.
Оператор for
абстрактно выглядит довольно просто:
for (инструкция_инициализации; условие; конечное_выражение)
инструкция;
Самый простой способ изначально понять, как работает оператор for
, – это преобразовать его в эквивалентный оператор while
:
{// обратите внимание на блок здесь
инструкция_инициализации; // используется для определения переменных,
// используемых в цикле
while (условие)
{
инструкция;
конечное_выражение; // используется для изменения переменной цикла перед
// повторным вычислением условия
}
} // переменные, определенные внутри цикла, здесь выходят из области видимости
Вычисление операторов for
Оператор for
вычисляется в трех частях:
- Сначала выполняется инструкция инициализации. Это происходит только один раз при запуске цикла. Инструкция инициализации обычно используется для определения и инициализации переменных. Эти переменные имеют «область видимости цикла», которая на самом деле является формой области видимости блока, где эти переменные существуют от точки определения до конца инструкции цикла. В нашем эквивалентном цикле
while
вы можете видеть, что инструкция инициализации находится внутри блока, содержащего цикл, поэтому переменные, определенные в инструкции инициализации, выходят за пределы области видимости, когда блок, содержащий цикл, заканчивается. - Во-вторых, для каждой итерации цикла вычисляется условие. Если оно истинно, инструкция выполняется. Если оно принимает значение
false
, цикл завершается, и выполнение продолжается со следующей инструкции за пределами цикла. - Наконец, после выполнения инструкции вычисляется конечное выражение. Обычно это выражение используется для увеличения или уменьшения переменных цикла, определенных в инструкции инициализации. После того, как конечное выражение было вычислено, выполнение возвращается ко второму шагу (и условие вычисляется снова).
Давайте посмотрим на пример цикла for
и обсудим, как он работает:
#include <iostream>
int main()
{
for (int count{ 1 }; count <= 10; ++count)
std::cout << count << ' ';
return 0;
}
Сначала мы объявляем переменную цикла с именем count
и инициализируем ее значением 1.
Во-вторых, вычисляется count <= 10
, и, поскольку count
равно 1, условие вычисляется как true
. Следовательно, выполняется инструкция, которая выводит 1 и пробел.
Наконец, вычисляется выражение ++count
, которое увеличивает значение count
до 2. Затем цикл возвращается ко второму шагу.
Теперь снова вычисляется count <= 10
. Поскольку count
имеет значение 2, условие вычисляет значение true
, поэтому цикл повторяется снова. Инструкция печатает 2 и пробел, и count
увеличивается до 3. Цикл продолжает повторяться, пока в конечном итоге count
не увеличится до 11, после чего count <= 10
вычисляется как false
, и цикл завершается.
Следовательно, эта программа выводит следующий результат:
1 2 3 4 5 6 7 8 9 10
В качестве примера давайте преобразуем приведенный выше цикл for
в эквивалентный цикл while
:
#include <iostream>
int main()
{
{ // блок здесь обеспечивает область видимости блока для count
int count{ 1 }; // наша инструкция инициализации
while (count <= 10) // наше условие
{
std::cout << count << ' '; // наша инструкция
++count; // наше конечное выражение
}
}
}
Выглядит не так уж плохо, правда? Обратите внимание, что здесь необходимы внешние фигурные скобки, потому что count
выходит за пределы области видимости, когда цикл заканчивается.
Начинающим программистам может быть трудно читать циклы for
, однако опытные программисты любят их, потому что они представляют собой очень компактный способ создания циклов со счетчиком со всей необходимой информацией о переменных цикла, условиях цикла и модификаторах счетчиков циклов, представленных заранее в одном месте. Это помогает уменьшить количество ошибок.
Больше примеров циклов
Вот пример функции, которая использует цикл for
для вычисления целочисленного возведения в степень:
// возвращает значение base^exponent - берегитесь переполнения!
int pow(int base, int exponent)
{
int total{ 1 };
for (int count{ 0 }; count < exponent; ++count)
total *= base;
return total;
}
Эта функция возвращает значение base
^exponent
(base
в степени exponent
).
Это цикл for
с простым увеличением счетчика на 1, с циклическим увеличением count
от 0 до exponent
(не включительно).
- Если
exponent
равен 0, циклfor
будет выполняться 0 раз, и функция вернет 1. - Если
exponent
равен 1, циклfor
выполнится 1 раз, и функция вернет 1 *base
. - Если
exponent
равен 2, циклfor
будет выполнен 2 раза, и функция вернет 1 *base
*base
.
Хотя большинство циклов for
увеличивают переменную цикла на 1, мы также можем уменьшать ее:
#include <iostream>
int main()
{
for (int count{ 9 }; count >= 0; --count)
std::cout << count << ' ';
return 0;
}
Эта программа напечатает следующий результат:
9 8 7 6 5 4 3 2 1 0
В качестве альтернативы мы можем с каждой итерацией изменять значение нашей переменной цикла более чем на 1:
#include <iostream>
int main()
{
for (int count{ 9 }; count >= 0; count -= 2)
std::cout << count << ' ';
return 0;
}
Эта программа напечатает следующий результат:
9 7 5 3 1
Ошибки на единицу
Одна из самых больших проблем, с которыми сталкиваются новички в циклах for
(и других циклах, в которых используются счетчики), – это ошибки «на единицу». Ошибки «на единицу» возникают, когда цикл повторяется на один раз больше или на один раз меньше, чем это необходимо.
Например:
#include <iostream>
int main()
{
// упс, мы использовали operator< вместо operator<=
for (unsigned int count{ 1 }; count < 5; ++count)
{
std::cout << count << ' ';
}
return 0;
}
Эта программа должна печатать «1 2 3 4 5», но она печатает только «1 2 3 4», потому что мы использовали неправильный оператор отношения.
Хотя наиболее частой причиной этих ошибок является использование неправильного оператора отношения, иногда они могут возникать при использовании префиксного инкремента или префиксного декремента вместо постфиксного инкремента или постфиксного декремента, или наоборот.
Пропущенные выражения
Циклы for
можно писать, опуская какие-либо или все инструкции или выражения. Например, в следующем примере мы опустим инструкцию инициализации и конечное выражение, оставив только условие:
#include <iostream>
int main()
{
int count{ 0 };
for ( ; count < 10; ) // нет инструкции инициализации и конечного выражения
{
std::cout << count << ' ';
++count;
}
return 0;
}
Этот цикл for
дает следующий результат:
0 1 2 3 4 5 6 7 8 9
Вместо того чтобы заставлять цикл for
выполнять инициализацию и инкремент, мы сделали это вручную. В этом примере мы сделали это исключительно для учебных целей, но бывают случаи, когда необходимо не объявлять переменную цикла (потому что она у вас уже есть) или не инкрементировать ее в конечном выражении (потому что вы увеличиваете ее другим способом).
Хотя вы не часто встретите это, стоит отметить, что следующий пример создает бесконечный цикл:
for (;;)
инструкция;
Приведенный выше пример эквивалентен следующему коду:
while (true)
инструкция;
Это может показаться немного неожиданным, поскольку вы, вероятно, ожидаете, что пропущенное условное выражение будет обрабатываться как ложное. Однако стандарт C++ явно (и непоследовательно) определяет, что пропущенное условное выражение в цикле for
должно рассматриваться как истинное.
Мы рекомендуем полностью избегать этой формы цикла for
и вместо нее использовать while (true)
.
Циклы for
с несколькими счетчиками
Хотя циклы for
обычно выполняют итерацию только по одной переменной, иногда они должны работать с несколькими переменными. Для этого программист может определить несколько переменных в операторе инициализации, а в конечном выражении может использовать оператор запятой для изменения значений нескольких переменных:
#include <iostream>
int main()
{
for (int x{ 0 }, y{ 9 }; x < 10; ++x, --y)
std::cout << x << ' ' << y << '\n';
return 0;
}
Этот цикл определяет и инициализирует две новые переменные: x
и y
. Он выполняет итерацию по x
в диапазоне от 0 до 9, и после каждой итерации x
увеличивается, а y
уменьшается.
Эта программа дает следующий результат:
0 9
1 8
2 7
3 6
4 5
5 4
6 3
7 2
8 1
9 0
Это почти единственное место в C++, где определение нескольких переменных в одном инструкции и использование оператора запятой считается приемлемой практикой.
Лучшая практика
Определение нескольких переменных (в инструкции инициализации) и использование оператора запятой (в конечном выражении) внутри оператора for
допустимо.
Вложенные циклы for
Как и другие типы циклов, циклы for
могут быть вложены внутрь других циклов. В следующем примере мы вкладываем цикл for
внутрь другого цикла for
:
#include <iostream>
int main()
{
for (char c{ 'a' }; c <= 'e'; ++c) // внешний цикл для букв
{
std::cout << c; // сначала печатаем нашу букву
for (int i{ 0 }; i < 3; ++i) // внутренний цикл для всех чисел
std::cout << i;
std::cout << '\n';
}
return 0;
}
При каждой итерации внешнего цикла внутренний цикл выполняется полностью. Следовательно, на выходе получается:
a012
b012
c012
d012
e012
Вот еще несколько подробностей о том, что здесь происходит. Сначала выполняется внешний цикл, и char c
инициализируется значением 'a'
. Затем вычисляется c <= 'e'
, что верно, поэтому выполняется тело цикла. Поскольку c
установлен в 'a'
, сначала выводится a. Затем полностью выполняется внутренний цикл (который выводит 0, 1 и 2). Затем печатается символ новой строки. Теперь тело внешнего цикла завершено, поэтому внешний цикл возвращается наверх, c
увеличивается до 'b'
, и условие цикла повторно вычисляется. Поскольку условие цикла по-прежнему выполняется, начинается следующая итерация внешнего цикла. Это напечатает "b012\n". И так далее.
Заключение
Операторы for
– это наиболее часто используемый цикл в языке C++. Несмотря на то, что его синтаксис обычно немного сбивает с толку начинающих программистов, вы будете видеть циклы for
так часто, что поймете их в кратчайшие сроки!
Операторы for
идеально подходят, если у вас есть переменная-счетчик. Если у вас нет счетчика, вероятно, лучше выбрать оператор while
.
Лучшая практика
Когда есть очевидная переменная цикла, предпочитайте использовать циклы for
вместо циклов while
.
Когда нет очевидной переменной цикла, предпочитайте использовать циклы while
вместо циклов for
.
Небольшой тест
Вопрос 1
Напишите цикл for
, который печатает каждое четное число от 0 до 20.
Ответ
for (int count{ 0 }; count <= 20; count += 2) std::cout << count << '\n';
Вопрос 2
Напишите функцию с именем sumTo()
, которая принимает целочисленный параметр с именем value
и возвращает сумму всех чисел от 1 до value
.
Например, sumTo(5)
должна вернуть 15, что равно 1 + 2 + 3 + 4 + 5.
Подсказка: при итерации от 1 до входного значения используйте переменную, объявленную вне цикла, для накопления суммы, подобно тому, как в приведенном выше примере pow()
используется переменная total
для накопления на каждой итерации возвращаемого значения.
Ответ
int sumTo(int value) { int total{ 0 }; for (int count{ 1 }; count <= value; ++count) total += count; return total; }
Вопрос 3
Что не так со следующим циклом for
?
// Вывести все числа от 9 до 0
for (unsigned int count{ 9 }; count >= 0; --count)
std::cout << count << ' ';
Ответ
Этот цикл
for
выполняется, покаcount >= 0
. Другими словами, он выполняется до тех пор, покаcount
не станет отрицательной. Однако, поскольку переменнаяcount
не имеет знака, она никогда не может стать отрицательной. Следовательно, этот цикл будет работать вечно (ха)! Как правило, рекомендуется избегать использования в циклах беззнаковых переменных без необходимости.