C++.Бархатный путь

       

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


Операторы цикла задают многократное исполнение.

ОператорЦикла ::= while (Выражение) Оператор

::= for (ОператорИнициализацииFor [Выражение] ; [Выражение] )Оператор

::= do Оператор while (Выражение);

ОператорИнициализацииFor ::= ОператорВыражение

::= Объявление

Прежде всего, отметим эквивалентные формы операторов цикла.

Оператор for (ОператорИнициализацииFor [ВыражениеA] ;[ВыражениеB]) Оператор

эквивалентен оператору

ОператорИнициализацииFor while (ВыражениеA) { Оператор

ВыражениеB ; }

Эти операторы называются операторами с предусловием.

Здесь следует обратить внимание на точку с запятой после выражения в теле оператора цикла while. Здесь выражение становится оператором.

А вот условие продолжения цикла в операторе цикла while опускать нельзя. В крайнем случае, это условие может быть представлено целочисленным ненулевым литералом.

Следует также обратить внимание на точку с запятой между двумя выражениями цикла for. В последнем примере они представлены символами ВыражениеA и ВыражениеB. Перед нами классический пример разделителя.

ОператорИнициализацииFor является обязательным элементом заголовка цикла. Обязательный оператор вполне может быть пустым.

Рассмотрим пример оператора цикла for: for ( ; ; ) ;

Его заголовок состоит из пустого оператора (ему соответствует первая точка с запятой) и разделителя, который разделяет два пустых выражения. Тело цикла - пустой оператор.

Пустое выражение, определяющее условие выполнения цикла for интерпретируется как всегда истинное условие. Отсутствие условия выполнения предполагает безусловное выполнение.

Синтаксис C++ накладывает на структуру нетерминального символа ОператорИнициализацииFor жёсткие ограничения:

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



Эта операция управляет последовательностью выполнения образующих оператор выражений.

Рассмотрим принципы работы этого оператора. Цикл состоит из четырёх этапов.


    Прежде всего, выполняется оператор инициализации цикла. Если он не пустой, выражение за выражением, слева направо. Этот этап можно назвать этапом инициализации цикла. Он выполняется один раз, в самом начале работы цикла. Затем вычисляется значение выражения, которое располагается слева от оператора инициализации. Это выражение называется выражением условия продолжения цикла. Сам этап можно назвать этапом определения условий выполнимости. Если значение этого выражения отлично от нуля (т.е. истинно), выполняется оператор цикла. Этот этап можно назвать этапом выполнения тела цикла. После этого вычисляются значения выражений, которые располагаются слева от выражения условия продолжения цикла. Этот этап можно назвать этапом вычисления шага цикла. На последних двух этапах могут измениться значения ранее определённых переменных. А потому следующий цикл повторяется с этапа определения условий выполнимости.


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

int qwe; for (qwe 10; ; ) {} // Оператор инициализатор построен на основе выражения сравнения. for (this; ; ) {} // Оператор инициализатор образован первичным выражением this. for (qwe; ; ) {} // Оператор инициализатор образован первичным выражением qwe. Ещё пример: int i = 0; int j; int val1 = 0; int val2; ::::: i = 25; j = i*2; ::::: for ( ; i 100; i++, j--) { val1 = i; val2 - j; }

Мы имеем оператор цикла for, оператор инициализации которого пуст, а условие выполнения цикла основывается на значении переменной, которая была ранее объявлена и проинициализирована. Заголовок цикла является центром управления цикла. Управление циклом основывается на внешней по отношению к телу цикла информации.

Ещё пример:



for ( int i = 25, int j = i*2; i 100; i++, j--) { val1 = i; val2 - j; }

Заголовок нового оператора содержит пару выражений, связанных операцией запятая. Тело оператора представляет всё тот же блок операторов. Что может содержать тело оператора? Любые операторы. Всё, что может называться операторами. От самого простого пустого оператора, до блоков операторов произвольной сложности! Этот блок живёт по своим законам. В нём можно объявлять переменные и константы, а поскольку в нём определена собственная область действия имён, то объявленные в блоке переменные и константы могут скрывать одноимённые объекты с более широкой областью действия имён.

А вот использование блока в операторе инициализации привело бы к дополнительным трудноразрешимым проблемам с новыми областями действия и видимости имён, вводимых в операторе инициализации. Часть переменных могла бы оказаться невидимой в теле оператора цикла.

Операция запятая позволяет в единственном операторе сделать всё то, для чего обычно используется блок операторов. В качестве составных элементов (в буквальном смысле выражений-операторов) этого оператора могут использоваться даже объявления. Таким образом, в заголовке оператора цикла 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);


Содержание раздела