7.9 – Инструкции for

Добавлено 29 мая 2021 в 16:06

Безусловно, наиболее часто используемой инструкцией цикла в C++ является оператор for. Оператор for (также называемый циклом for) предпочтителен, когда у нас есть очевидная переменная цикла, потому что он позволяет нам легко и кратко определять, инициализировать, тестировать и изменять значение переменных цикла.

Начиная с C++11, существует два разных типа циклов for. В этом уроке мы рассмотрим классический оператор for, а новый оператор for на основе диапазона в следующем уроке (9.19 – Циклы for-each), как только рассмотрим некоторые другие необходимые темы, такие как массивы и итераторы.

Оператор for абстрактно выглядит довольно просто:

for (инструкция_инициализации; условие; конечное_выражение)
   инструкция;

Самый простой способ изначально понять, как работает оператор for, – это преобразовать его в эквивалентный оператор while:

{// обратите внимание на блок здесь
    инструкция_инициализации; // используется для определения переменных,
                              // используемых в цикле
    while (условие)
    {
        инструкция;
        конечное_выражение; // используется для изменения переменной цикла перед
                            // повторным вычислением условия
    }
} // переменные, определенные внутри цикла, здесь выходят из области видимости

Вычисление операторов for

Оператор for вычисляется в трех частях:

  1. Сначала выполняется инструкция инициализации. Это происходит только один раз при запуске цикла. Инструкция инициализации обычно используется для определения и инициализации переменных. Эти переменные имеют «область видимости цикла», которая на самом деле является формой области видимости блока, где эти переменные существуют от точки определения до конца инструкции цикла. В нашем эквивалентном цикле while вы можете видеть, что инструкция инициализации находится внутри блока, содержащего цикл, поэтому переменные, определенные в инструкции инициализации, выходят за пределы области видимости, когда блок, содержащий цикл, заканчивается.
  2. Во-вторых, для каждой итерации цикла вычисляется условие. Если оно истинно, инструкция выполняется. Если оно принимает значение false, цикл завершается, и выполнение продолжается со следующей инструкции за пределами цикла.
  3. Наконец, после выполнения инструкции вычисляется конечное выражение. Обычно это выражение используется для увеличения или уменьшения переменных цикла, определенных в инструкции инициализации. После того, как конечное выражение было вычислено, выполнение возвращается ко второму шагу (и условие вычисляется снова).

Давайте посмотрим на пример цикла 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 не имеет знака, она никогда не может стать отрицательной. Следовательно, этот цикл будет работать вечно (ха)! Как правило, рекомендуется избегать использования в циклах беззнаковых переменных без необходимости.

Теги

C++ / CppforLearnCppwhileДля начинающихОбучениеПрограммированиеЦикл

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.