C++. Бархатный путь. Часть 1 - Примеры использования оператора цикла for

ОГЛАВЛЕНИЕ

Примеры использования оператора цикла for

Рассмотрим несколько примеров. Так, в ходе выполнения оператора цикла

int i;
for (i = 0; i < 10; i++)
{
int j = 0; j += i;
}

десять раз будет выполняться оператор определения переменной j. Каждый раз это будут новые объекты. Каждый раз новой переменной заново будет присваиваться новое значение одной и той же переменной i, объявленной непосредственно перед оператором цикла for.

Объявление переменной i можно расположить непосредственно в теле оператора-инициализатора цикла:

for (int i = 0; i < 10; i++)
{
int j = 0; j += i;
}

И здесь возникает одна проблема. Дело в том, что тело оператора цикла for (оператор или блок операторов) имеет ограниченную область действия имён. А область действия имени, объявленного в операторе-инициализаторе, оказывается шире этой области.

Заголовок цикла for в C++ - центр управления циклом. Здесь следят за внешним миром, за тем, что происходит вне цикла. И потому все обращения к переменным и даже их новые объявления в заголовке цикла относятся к "внешней" области видимости. Следствием такого допущения (его преимущества далеко не очевидны) является правило соотнесения имени, объявленного в заголовке и области его действия.

По отношению к объявлению переменной в заголовке оператора цикла for, правило соотнесения гласит, что область действия имени, объявленного в операторе инициализации цикла for, располагается в блоке, содержащем данный оператор цикла for.

А вот область действия имени переменной j при этом остаётся прежней.

В теле оператора for может быть определена одноимённая переменная:

for (int i = 0; i < 10; i++)
{
int i = 0; i += i;
}

Пространство имени переменной в операторе цикла ограничено блоком из двух операторов. В этом пространстве переменная, объявленная в заголовке, оказывается скрытой одноимённой переменной.

Десять раз переменная i из оператора-инициализатора цикла будет заслоняться одноимённой переменной из оператора тела цикла. И всякий раз к нулю будет прибавляться нуль.

Ещё один пример. Два расположенных друг за другом оператора цикла for содержат ошибку

for (int i = 0, int j = 0; i < 100; i++, j--)
{
// Операторы первого цикла.
}
for (int i = 0, int k = 250; i < 100; i++, k--)
{
// Операторы второго цикла.
}

Всё дело в том, что, согласно правилу соотнесения имён и областей действия имён в операторе цикла for, объявления переменных в заголовке цикла оказываются в общем пространстве имён. А почему, собственно, не приписать переменные, объявленные в заголовке цикла блоку, составляющему тело цикла? У каждого из альтернативных вариантов соотнесения имеются свои достоинства и недостатки. Однако выбор сделан, что неизбежно ведёт к конфликту имён и воспринимается как попытка переобъявления ранее объявленной переменной.

Эту самую пару операторов for можно переписать, например, следующим образом:

for (int i = 0, int j = 0; i < 100; i++, j--)
{
// Здесь располагаются операторы первого цикла.
}
for (i = 0, int k = 250; i < 100; i++, k--)
{
// Здесь располагаются операторы второго цикла.
}

Здесь нет ошибок, но при чтении программы может потребоваться дополнительное время для того, чтобы понять, откуда берётся имя для выражения присвоения i = 0 во втором операторе цикла. Кроме того, если предположить, что операторы цикла в данном контексте реализуют независимые шаги какого-либо алгоритма, то почему попытка перемены мест пары абсолютно независимых операторов сопровождается сообщением об ошибке:

for (i = 0, int k = 250; i < 100; i++, k--)
{
// Здесь располагаются операторы второго цикла.
}
for (int i = 0, int j = 0; i < 100; i++, j--)
{
// Здесь располагаются операторы первого цикла.
}

Очевидно, что в первом операторе оказывается необъявленной переменная i. Возможно, что не очень удобно, однако, в противном случае, в центре управления циклом трудно буден следить за внешними событиями. В конце концов, никто не заставляет программиста располагать в операторе инициализации объявления переменных. Исходная пара операторов может быть с успехом переписана следующим образом:

int i, j, k;
:::::
for (i = 0, k = 250; i < 100; i++, k--)
{
// Здесь располагаются операторы второго цикла.
}
for (i = 0, j = 0; i < 100; i++, j--)
{
// Здесь располагаются операторы первого цикла.
}

А вот ещё один довольно странный оператор цикла, в котором, тем не менее, абсолютно корректно соблюдены принципы областей действия имён, областей видимости имён, а также соглашения о соотнесении имён и областей их действия:

for (int x; x < 10; x++) {int x = 0; x++;}

Так что не забываем о том, что область действия имён в заголовке цикла шире от области действия имён в теле цикла. И вообще, если можно, избавляемся от объявлений в заголовке оператора цикла.

Оператор цикла do … while называется оператором цикла с постусловием. От циклов с предусловием он отличается тем, что сначала выполняется оператор (возможно, составной), а затем проверяется условие выполнения цикла, представленное выражением, которое располагается в скобках после ключевого слова while. В зависимости от значения этого выражения возобновляется выполнение оператора. Таким образом, всегда, по крайней мере один раз, гарантируется выполнение оператора цикла.

int XXX = 0;
do {cout << XXX << endl; XXX++;} while (XXX < 0);