Основные синтаксические конструкции — различия между версиями
Korvin (обсуждение | вклад) м (→Цикл while) |
Raw mat (обсуждение | вклад) |
||
(не показаны 15 промежуточных версий 11 участников) | |||
Строка 53: | Строка 53: | ||
По поводу нуля следует поговорить отдельно. C++ не является в полной мере объектно-ориентированным языком, фактически, в нем ОО система является абстракцией, позволяющей оперировать все теми же понятиями стандартных типов данных и памяти, но на более высоком уровне. Все что мы имеем на этапе выполнения программы, это кусочки памяти, которые мы считаем объектами. Поскольку числа в нем являются совершенно определенной сущностью, подобные вольности возможны. | По поводу нуля следует поговорить отдельно. C++ не является в полной мере объектно-ориентированным языком, фактически, в нем ОО система является абстракцией, позволяющей оперировать все теми же понятиями стандартных типов данных и памяти, но на более высоком уровне. Все что мы имеем на этапе выполнения программы, это кусочки памяти, которые мы считаем объектами. Поскольку числа в нем являются совершенно определенной сущностью, подобные вольности возможны. | ||
− | В языке К++ все наоборот. Все с чем он оперирует — это объекты. Провести грань между стандартными типами данных и пользовательскими невозможно. Числа, что фигурируют в программе так же являются объектами. Даже числовые константы которые записываются в выражениях — это тоже объекты (это уже было показано в [[Введение, или краткий обзор#Расширение классов|кратком обзоре]]). За основу логики языка принимается факт существования объекта. Если объекта не существует — значение ложно, если существует — истинно (кроме описанных выше исключений). Оператор <tt>if</tt> помимо чистой логики, служит для определения существования объекта. Получается, что при таком подходе, делать для нуля исключение нерационально, и более того — это может привести к ошибкам. Возьмем к примеру код: | + | В языке К++ все наоборот. Все с чем он оперирует — это объекты. Провести грань между стандартными типами данных и пользовательскими невозможно. Числа, что фигурируют в программе так же являются объектами. Даже числовые константы которые записываются в выражениях — это тоже объекты (это уже было показано в [[Введение, или краткий обзор#Расширение классов|кратком обзоре]]). За основу логики языка принимается факт существования объекта. Если объекта не существует — значение ложно, если существует — истинно (кроме описанных выше исключений). Оператор <tt>'''if'''</tt> помимо чистой логики, служит для определения существования объекта. Получается, что при таком подходе, делать для нуля исключение нерационально, и более того — это может привести к ошибкам. Возьмем к примеру код: |
<source lang="kpp"> | <source lang="kpp"> | ||
Строка 285: | Строка 285: | ||
<source lang="kpp"> | <source lang="kpp"> | ||
var i = 0, s = 0; | var i = 0, s = 0; | ||
− | s += | + | s += i++ while i < 100; |
</source> | </source> | ||
Строка 343: | Строка 343: | ||
Ключевое слово <tt>'''break'''</tt> служит для безусловного выхода за пределы цикла, независимо от основного условия и количества "оставшихся" итераций. Оно будет рассмотрено ниже. | Ключевое слово <tt>'''break'''</tt> служит для безусловного выхода за пределы цикла, независимо от основного условия и количества "оставшихся" итераций. Оно будет рассмотрено ниже. | ||
− | '''Примечание:''' Несмотря на то, что синтаксис цикла и логика его работы очень похожи на те, что присутствуют в языке C++, существуют и отличия. В частности, при использовании вложенных циклов, допускается использование одного и того же имени управляющей переменной. Логика их использования соответствует логике [[Функции#Область видимости|применения локальных переменных в функциях]]: | + | '''Примечание:''' Несмотря на то, что синтаксис цикла и логика его работы очень похожи на те, что присутствуют в языке C++, существуют и отличия. В частности, при использовании вложенных циклов, допускается (хоть и не рекомендуется) использование одного и того же имени управляющей переменной. Логика их использования соответствует логике [[Функции#Область видимости|применения локальных переменных в функциях]]: |
<source lang="kpp" line=1> | <source lang="kpp" line=1> | ||
for (var i = 1; i < 10; i++) { | for (var i = 1; i < 10; i++) { | ||
− | + | puts("(1) i = #{i}"); | |
for (var i = 1; i < 5; i++) | for (var i = 1; i < 5; i++) | ||
− | + | puts("(2) i = #{i}"); | |
− | + | puts("(3) i = #{i}"); | |
} | } | ||
</source> | </source> | ||
Строка 362: | Строка 362: | ||
//используется внешняя переменная | //используется внешняя переменная | ||
s += i; | s += i; | ||
− | + | puts("#{i}: s = #{s}"); | |
ary.push(i); | ary.push(i); | ||
} | } | ||
Строка 378: | Строка 378: | ||
str += "#{li.object}, "; | str += "#{li.object}, "; | ||
} | } | ||
− | str[str.size()- | + | str[str.size()-2] = "]"; |
− | + | puts(str); | |
</source> | </source> | ||
Строка 393: | Строка 393: | ||
В некоторых языках программирования присутствует специальная синтаксическая конструкция цикла <tt>'''foreach'''</tt>. Как правило, она применяется в случаях, когда необходимо перебрать все значения некоторой коллекции и выполнить код, дословно, "для каждого из значений". | В некоторых языках программирования присутствует специальная синтаксическая конструкция цикла <tt>'''foreach'''</tt>. Как правило, она применяется в случаях, когда необходимо перебрать все значения некоторой коллекции и выполнить код, дословно, "для каждого из значений". | ||
− | Учитывая то, что язык K++ ориентирован на использование [[Блоки|блоков]], непосредственная надобность в такой конструкции отпадает, поскольку ее функционал с успехом может быть реализован и даже превзойден стандартными методами коллекций, такими как <tt>each()</tt>, <tt>each_pair()</tt> и <tt> | + | Учитывая то, что язык K++ ориентирован на использование [[Блоки|блоков]], непосредственная надобность в такой конструкции отпадает, поскольку ее функционал с успехом может быть реализован и даже превзойден стандартными методами коллекций, такими как <tt>each()</tt>, <tt>each_pair()</tt> и <tt>each_with_index()</tt>. Первый метод делает ровно то же самое что и традиционная конструкция <tt>'''foreach'''</tt>, второй вызывает блок, передавая ему пару из индекса и соответствующему индексу элемента из коллекции. Третий метод передает только сам индекс. |
Тем не менее, в целях удобства программистов, была создана системная функция, с названием <tt>foreach()</tt>, которая ведет себя подобно оператору. На деле она всего лишь вызывает метод <tt>each()</tt> у передаваемой ей коллекции: | Тем не менее, в целях удобства программистов, была создана системная функция, с названием <tt>foreach()</tt>, которая ведет себя подобно оператору. На деле она всего лишь вызывает метод <tt>each()</tt> у передаваемой ей коллекции: | ||
<source lang="kpp"> | <source lang="kpp"> | ||
− | function void foreach(collection, block b) { | + | function void foreach(const collection, block b) { |
collection.each(b); | collection.each(b); | ||
} | } | ||
Строка 476: | Строка 476: | ||
var myObject = new MyClass; | var myObject = new MyClass; | ||
− | while (myObject.Compacted) | + | while (!myObject.Compacted) |
myObject.Compact(); | myObject.Compact(); | ||
Строка 495: | Строка 495: | ||
//цикл for в роли цикла while | //цикл for в роли цикла while | ||
var myObject = new MyClass; | var myObject = new MyClass; | ||
− | for ( ; myObject.Compacted; ) | + | for ( ; !myObject.Compacted; ) |
myObject.Compact(); | myObject.Compact(); | ||
Текущая версия на 14:43, 13 июля 2013
В этой главе будут рассмотрены основные синтаксические конструкции, которые так или иначе присутствуют в любом современном языке программирования, будь то функциональный или объектно-ориентированный язык. Это констркции условного перехода и циклы. Без подобных конструкций (или их аналогов) невозможно написать программу, сколько-нибудь сложнее чем "Hello world".
Содержание |
[править] Условный оператор
Условный оператор — это основа основ любого языка программирования. На основании условия, то есть значения некоторого выражения, принимается решение, куда следует двигаться программе. В основе всех условных операторов лежит булева логика, при которой любое утверждение сводится к двум понятиям: истина и ложь. Если условие истинно, то управление программы движется в одну сторону. Если условие ложно — в другую. Таким образом, в каждый момент времени делается выбор всего из двух направлений. Более сложные условия строятся путем последовательного прохождения элементарных, двоичных условий.
[править] Оператор if
В языке K++, для проверки условия служит оператор if, так же называемый условным оператором. Синтаксис этого оператора знаком наверное любому человеку, который имеет хоть какое-то представление о языках программирования: <source lang="kpp"> if (/* выражение условия */) {
/* тело оператора */
} </source>
Если в коде программы встречается условный оператор, то первым делом происходит вычисление значения его условия — выражения, заключенного в круглые скобки. В качестве такого выражения, может быть указано любое выражение, которое возвращает значение. Если значение условного выражения можно трактовать как истину, то управление передается телу оператора, заключенному в фигурные скобки (в случае единственного выражения в теле оператора, фигурные скобки можно опустить). Если же значение условия ложно — управление передается следующему выражению, стоящему после условного оператора (то есть, тело оператора просто пропускается).
Истинным, считается любой существующий объект, а так же логические операторы сравнения и отношения (такие как ==, !=, < и т. д), если их значение истинно (то есть, объекты соответствуют указанному отношению), а так же специальное ключевое слово true, которое считается истинным всегда. Исключением является объект типа bool, имеющий в данный момент значение false.
Ложными, соответственно, считаются несозданные объекты (пустые указатели), а так же операторы отношения, если их значение ложно (объекты не соответствуют отношению). Ложными так же считаются объекты, соответствующие ключевым словам false и null. Опять же, исключением является объект типа bool, если он имеет значение true.
Для ясности приведем несколько выражений, а так же укажем соответствующие им логические значения: <source lang="kpp"> //Выражение булево значение true true false false null false var x; x false var y; y = 1; true var int z; z true var bool x; false var bool x; x = true; true 0 true 1 true "hello world" true 0 == 1 false 5 < 6 true 2 > 3 false 5 >= 5 true 'a' != 'b' true </source>
Обратите внимание на следующие моменты:
- Неинициализированная динамическая переменная x считается ложной (равна null)
- Инициализированная динамическая переменная y считается истинной (объект создан)
- Неинициализированная статическая переменная z считается истинной (объект создан)
- Переменная типа bool имеет значение по умолчанию false (хотя объект создан)
- 0, так же как любой другой созданный объект считается истиной, в отличие от C++.
- условие 5 >= 5 считается истиной, так как формулируется как "5 больше 5 или равно 5"
По поводу нуля следует поговорить отдельно. C++ не является в полной мере объектно-ориентированным языком, фактически, в нем ОО система является абстракцией, позволяющей оперировать все теми же понятиями стандартных типов данных и памяти, но на более высоком уровне. Все что мы имеем на этапе выполнения программы, это кусочки памяти, которые мы считаем объектами. Поскольку числа в нем являются совершенно определенной сущностью, подобные вольности возможны.
В языке К++ все наоборот. Все с чем он оперирует — это объекты. Провести грань между стандартными типами данных и пользовательскими невозможно. Числа, что фигурируют в программе так же являются объектами. Даже числовые константы которые записываются в выражениях — это тоже объекты (это уже было показано в кратком обзоре). За основу логики языка принимается факт существования объекта. Если объекта не существует — значение ложно, если существует — истинно (кроме описанных выше исключений). Оператор if помимо чистой логики, служит для определения существования объекта. Получается, что при таком подходе, делать для нуля исключение нерационально, и более того — это может привести к ошибкам. Возьмем к примеру код:
<source lang="kpp"> if (x) {
/* код, соответствущий существованию переменной x */
} </source>
Подразумевается, что тело оператора будет выполнено в случае, если передаваемый объект (вероятнее всего динамическая переменная) инициализирован и существует. Но что будет, если мы передадим в качестве объекта динамическую переменную, содержащую объект типа int, равный нулю? Если бы реализация языка обрабатывала значение 0 как ложь, то логика оператора if в частости и всей программы в целом, зависела бы от фактического значения переменной, а соответственно была бы неопределенной и даже непредсказуемой. Получалось бы, что для одних переменных, оператор работает одним образом, а для других — другим. Отладка подобной программы может занять многие часы. А все из-за одной условности.
Таким образом, можно заключить, что оператор if следует применять для проверки логического выражения, либо для проверки существования переменной. Если мы хотим проверить равенство переменной нулю, то это необходимо делать явно: <source lang="kpp"> if (x == 0) {
/* что нужно делать, если x равен 0 */
} </source>
[править] Оператор if-else
Если нам необходимо организовать ветвление, то есть в случае истинности, выполнить один участок кода, а в случае ложности другой, то применяется расширенная форма оператора if-else: <source lang="kpp"> if (/* выражение условия */) {
/* код, соответствущий истине */
} else {
/* код, соответствущий лжи */
} </source>
Если условие истинно, то будет выполнен первый блок, если ложно то второй.
Условные операторы, подобно многим другим, позволяют организовывать вложения, когда в тело одного условного оператора вкладывается один или несколько других условных операторов, например:
<source lang="kpp">
if (/* условие 1 */) {
/* если условие 1 истинно: */ if (/* условие 2 */) { /* если условие 2 истинно */ } if (/* условие 3 */) { /* если условие 3 истинно */ }
} else {
/* если условие 1 ложно: */ if (/* условие 4 */) { /* если условие 4 истинно */ } else { /* код, соответствущий лжи условия 4 */ }
} </source> Таким образом можно строить разветвленные деревья условий, которые и реализуют алгоритмы программ.
[править] Оператор if-elsif-else
Существует так же третья форма условного оператора, которая позволяет проверять несколько условий в единой, более эффективной форме. Это конструкция if-elsif-else: <source lang="kpp"> if (/* условие 1 */) {
/* код, соответствущий истине условия 1*/
} elsif (/*условие 2*/) {
/* код, соответствущий истине условия 2*/
... } else {
/* код, соответствущий лжи */
} </source> Она работает так: сначала проверяется условие 1. Если оно истинно, то выполняется код в первом блоке, а затем управление выходит за рамки всей связки. Если условие 1 ложно, то проверяется условие 2, и т. д. Таким образом, вышеописанная конструкция функционально эквивалентна следующей конструкции, однако более компактна.
<source lang="kpp"> if (/* условие 1 */) {
/* если условие 1 истинно */
} else {
if (/* условие 2 */) { /* если условие 2 истинно */ } else { if (/* условие 3 */) { /* если условие 3 истинно */ } else { ... } }
} </source>
[править] Постфиксные операторы
Существует так же более лаконичная форма оператора if, удобная для записи коротких, однострочных условий. При этом, само условие записывается после выражения, которое оно должно проверить:
<source lang="kpp"> /* выражение */ if /* условие */; </source>
При такой записи, окружать условие скобками не требуется. Проверяемое выражение должно быть единственным, то есть не содержать оператора точка с запятой (;). Такие условия занимают меньше места в программе и помогут сделать ваш код более чистым:
<source lang="kpp"> var even_sum = 0; for (var i = 1; i < 100; i++)
even_sum += i if i % 2 == 0; //сумма всех четных чисел
</source>
Для проверки "невыполнения" условия есть специальная форма постфиксного оператора — unless. Выражение при таком операторе будет выполняться только если условие ложно. Таким образом, смысл оператора прямо противоположен оператору if. Конечно, эти два оператора взаимозаменяемы. Конструкция do_smth if x; делает то же самое что do_smth unless !x;.
Перепишем предыдущий код с использованием оператора unless:
<source lang="kpp"> var even_sum = 0; for (var i = 1; i < 100; i++)
even_sum += i unless i % 2 != 0; //сумма всех четных чисел
</source>
[править] Тернарный условный оператор
В некоторых случаях, может возникнуть ситуация, при которой необходимо присвоить значение некоторой переменной на основании условия. То есть, при истинности условия присвоить одно значение, а при ложности — другое. Это можно осуществить двумя способами. Первый способ — использовать обычный условный оператор: <source lang="kpp"> var x = 5; var y = 0; if (x > 3)
y = 3;
else
y = -1;
</source>
Не будем уточнять, зачем кому-то мог понадобиться подобный "гениальный" код, а отметим лишь то, что операция присвоения занимает 4 строки при соблюдении правил форматирования кода. В тех случаях, когда тела условного оператора представляют собой простые выражения "в одну строчку", особенно если, как уже было сказано выше, происходит присвоение, разумно применять специальную форму условного оператора — тернарный оператор ?:. Тогда, тот же код будет выглядеть так: <source lang="kpp"> var x = 5; var y = 0; y = x > 5 ? 3 : -1; </source>
Это единственный в языке К++ тернарный оператор, работающий с тремя выражениями сразу. Первое выражение — это условие; оно располагается слева, до знака вопроса. Между знаком вопроса и знаком двоеточия расположено выражение, которое должно подставляться, в случае истинности условия. Последним располагается выражениие, подставляющееся в случае ложности.
Данная форма оператора очень удобна для записи непосредственно внутри выражений, фактических параметров в вызовах функций, либо любых других выражениях. Его даже можно применять в операторе подстановки #{ } в строках. Можно его применять и без возврата значения, например так: <source lang="kpp"> while (true) {
CheckState() != State.OK ? Signal() : Sleep();
} </source>
Примечание: Работать такой код будет если и Signal() и Sleep() возвращают значения.
[править] Оператор множественного выбора (switch)
В тех случаях, когда требуется сравнить значение некоторого выражения с большим количеством возможных значений, применять обычные условные операторы неразумно. Код получится громоздким и трудно читаемым, а самое главное — изменять такой код будет очень сложно. В таких случаях, на помощь программисту приходит оператор множественного выбора, или оператор switch.
Предположим, что перед нами стоит такая задача: функция получает некоторый числовой код ошибки и на основании него должна выполнять различные действия. В частности, выводить соответствующие сообщения. Если от нас требуется только вернуть соответствующую коду ошибки строку, проще всего и эффективнее, разумеется, применять хеши, но мы будем считать что требуется сделать что-то еще.
Конструкция множественного выбора реализуется следующим образом: указывается ключевое слово switch, после которого, в круглых скобках указывается выражение-селектор, которое мы будем анализировать. Затем, ставятся фигурные скобки switch-блока, в теле которого могут быть только конструкции, относящиеся непосредственно к оператору. В нашем случае это будет примерно так: <source lang="kpp"> function ProcessError(const int errno) {
switch (errno) { /* switch блок */ }
} </source>
Далее, мы должны определить относящиеся к делу случаи, а именно, перечислить конкретные значения, с которыми мы будем сравнивать код ошибки и на основании этого сравнения будем выполнять некоторые операции. Это осуществляется с помощью подоператора case, который записывается так: <source lang="kpp"> case /* выражение сравнения */: /* обработчик */ </source>
Выражение сравнения — это любое выражение, которое будет сравниваться с селектором. В качестве такого выражения может быть выбрано не одно, а сразу несколько условий. Тогда они должны отделяться друг от друга запятой: <source lang="kpp"> case /*условие1*/, /*условие2*/, ... /*условие N*/: /* обработчик */ </source>
В качестве условия может так же быть указан интервал. Тогда будет проверяться, попадает ли значение выражения селектора в заданный интервал. Например, если нам необходимо анализировать численное выражение, то один из операторов case может выглядеть так: <source lang="kpp"> case 1, 2, 5 .. 10, 12: /* обработчик */ </source>
Для срабатывания обработчика достаточно, чтобы сработало хотябы одно из условий, перечисленных в списке; тогда управление будет передано коду обработчика. Обработчиком может быть либо отдельное выражение, либо блок. Во втором случае, его необходимо заключить в фигурные скобки. В отличие от C++, после обработчика управление передается за пределы оператора switch, а не на следующее сравнение. То есть, логика работы более близка к паскалевской конструкции case.
Для безусловного выхода из любого места обработчика, может применяться ключевое слово break. При этом, управление будет передано коду, следующему за оператором switch.
Если требуется рассмотреть отдельный случай, когда ни одно из условий не было удовлетворено (то есть, ни один из операторов case не сработал), применяется действие по умолчанию. Для этого, необходимо записать ключевое слово default, а затем через двоеточие указать обработчик. Естественно, никакого выражения сравнения тут нет.
Примечание: при использовании обработчиков по умолчанию, следует иметь в виду что:
- на кажый оператор switch может быть указан только один обработчик по умолчанию
- обработчик по умолчанию должен быть записан в самом конце конструкции
Обобщив вышесказанное, напишем наконец, реализацию задуманной нами функции:
<source lang="kpp" line="1">
function ProcessError(const int errno) {
switch (errno) { case AGAIN: { Log("требуется повтор операции"); /* запрашиваем у пользователя подтверждение на повтор */ } case ACCESS_DENIED: Log("ошибка доступа"); case REQUEST_TIMEOUT, TIMEOUT: Log("таймаут"); default: Log("неизвестный код ошибки: #{errno}"); }
} </source>
- 3-6
- Поскольку выражение обработчик сложное, то есть состоит более чем из одной конструкции, мы заключили его в фирурные скобки.
- 9
- Для случаев REQUEST_TIMEOUT и TIMEOUT был использован один обработчик. Подразумевается, что коды, соответствующие данным ошибкам разные, но программист решил их объединить в одно целое.
[править] Циклы
Циклы представляют собой еще одну неотъемлемую часть любого языка программирования. Сущность циклов заключается в том, что некоторый участок кода (тело цикла), на основании некоторого условия выполняется определенное количество раз (итераций). Различают циклы с предпроверкой условия и с постпроверкой. В первом случае, сначала проверяется условие, а затем выполняется переход. Во втором случае, тело цикла выполняется как минимум один раз, после чего проверяется условие и принимается решение о повторении, либо о выходе.
[править] Цикл while
Простейшим из циклов является цикл while, который выглядит следующим образом: <source lang="kpp"> while (/*условие*/) {
/* тело цикла */
} </source>
Цикл while является циклом с предпроверкой условия, то есть, сначала проверяется значение выражения в условии цикла, а затем выполняется переход на тело цикла (если условие истинно), либо за пределы цикла (если ложно). Схематически, поведение цикла изображено на блочной диаграмме слева.
Условием цикла может быть любое выражение, которое возвращает значение. При этом оно трактуется как логическое выражение, что определяет то же самое поведение, что и у условных операторов. Тело цикла может представлять собой одну или несколько конструкций. В случае нескольких конструкций, тело цикла следует заключить в фигурные скобки.
Тело цикла представляет собой контекст. Таким образом, переменные, объявленные в теле цикла, не будут видны за его пределами. Разумеется, переменные объявленные выше по иерархии, будут видны в теле цикла. В целом, с этой точки зрения все происходит точно так же как и в теле самих функций.
В теле цикла могут использоваться любые конструкции, что и в теле самих функций. Допускаются вложенные циклы.
Примечание: Существует так же постфиксная форма оператора while, принципы использования которой схожи с таковыми в случае постфиксных условных операторов. Например так можно посчитать сумму чисел от одного до ста практически "в одно действие":
<source lang="kpp"> var i = 0, s = 0; s += i++ while i < 100; </source>
[править] Цикл for
Более сложным примером является цикл for. Как правило, он применяется в случаях, когда есть некоторая переменная изменяющая свое значение в определенных пределах и сохраняющая свое значение в течение всей итерации. В конструкции цикла for собраны все необходимые операции, что позволяет оперировать переменной более удобным образом. Опять же, исключается возможность ошибки, при которой программист забыл выполнить одну из операций.
Цикл for работает следующим образом. Сначала выполняется код инициализации переменной цикла (ее еще называют управляющей переменной). Как видно из блочной диаграммы, это происходит только один раз, в самом начале. Затем, проверяется условие. Если условие истинно, управление передается телу цикла. Подразумевается, что в нем будут производиться некоторые действия, учиывающие текущее значение переменной цикла. После выполнения тела цикла, производится приращение управляющей переменной. Далее управление опять передается коду проверки условия; так повторяется до тех пор пока значение выражения условия не станет ложным.
В отличие от паскаля, цикл for совершенно не обязательно должен работать с числами. В роли управляющей переменной может выступать любой объект. Синтаксис оператора for выглядит так:
<source lang="kpp">
for (/* инициализатор */; /* условие */ ; /* приращение */) {
/* тело цикла */
} </source>
Выражение-инициализатор представляет собой выражение, объявляющее управляющую переменную цикла, либо просто инициализирующее некоторую переменную начальным значением. Объявляемая управляющая переменная будет иметь область видимости, соответствующую телу цикла, и не будет видна за его пределами. Внешняя переменная, естественно, будет видна в пределах той области, в которой она была объявлена. В этом случае, цикл просто использует ее, изменяя значения. При выходе из цикла, внешняя переменная будет иметь значение, соответствующее последней итерации, плюс одно приращение (см. диаграмму).
Если требуется объявление, то следует использовать конструкцию объявления переменной, точно такую же как и в обычной практике. То есть, правила объявления переменных и инициализации те же. Выражение-условие содержит некоторое выражение (обычно оператор отношения), которое должно менять свое значение в зависимости от значения управляющей переменной. Наконец, выражение приращения используется для изменения значения управляющей переменной на некоторую атомарную величину. В случае целочисленного типа управляющей переменной, для этого, как правило, используется оператор ++ (поскольку выражение приращения выполняется отдельно от тела, здесь не имеет значения, какую форму оператора использовать).
В зависимости от ситуации, некоторые из трех вышеперечисленных выражений могут быть опущены. Следующие циклы, по сути, являются функционально эквивалентными:
<source lang="kpp">
for (var i = 0; i < 10; i++) { /* тело */ } </source> |
<source lang="kpp">
var i = 0; for (; i < 10; i++) { /* тело */ } </source> |
<source lang="kpp">
for (var i = 0; ; i++) { if (i >= 10) break; /* тело */ } </source> |
<source lang="kpp">
for (var i = 0; i < 10; ) { /* тело */ i++; } </source> |
Подобные изменения могут быть оправданными в некоторых задачах, где требуется более тонкое управление поведением цикла. Чаще всего применяются приемы использования внешней переменной и вынесение приращения в тело цикла.
Ключевое слово break служит для безусловного выхода за пределы цикла, независимо от основного условия и количества "оставшихся" итераций. Оно будет рассмотрено ниже.
Примечание: Несмотря на то, что синтаксис цикла и логика его работы очень похожи на те, что присутствуют в языке C++, существуют и отличия. В частности, при использовании вложенных циклов, допускается (хоть и не рекомендуется) использование одного и того же имени управляющей переменной. Логика их использования соответствует логике применения локальных переменных в функциях: <source lang="kpp" line=1> for (var i = 1; i < 10; i++) {
puts("(1) i = #{i}"); for (var i = 1; i < 5; i++) puts("(2) i = #{i}"); puts("(3) i = #{i}");
} </source>
Приведем несколько более практичных примеров использования цикла в различных задачах: <source lang="kpp" line=1> var s = 0, var i = 1; var ary = []; for ( ; i < 10; i++) {
//используется внешняя переменная s += i; puts("#{i}: s = #{s}"); ary.push(i);
}
var list lst; for (var i = 0; i < ary.size(); ) {
lst.push(ary[i]); i += 2; //приращение вынесено в тело
}
var str = '['; for (var li = lst.begin(); ; ++i) {
if (li == lst.end()) //условие вынесено в тело break; str += "#{li.object}, ";
} str[str.size()-2] = "]"; puts(str); </source>
- 1-8
- Для демонстрации возможности частичного указания выражений цикла, приведен пример, в цикле которого отсутствует выражение-инициализатор. Для работы, цикл использует внешнюю переменную. В теле цикла считается сумма чисел от 0 до 9; результат накапливается опять же, во внешней переменной s. Далее, в массив ary добавляются текущие значения управляющей переменной, то есть i.
- 10-14
- В теле этого цикла, мы копируем каждый нечетный элемент массива ary в список. Обратите внимание на выражение приращения (в данном случае оно вынесено в тело цикла), которое на каждой итерации добавляет к переменной индекса 2.
- 16-23
- Для отображения содержимого списка в удобной для восприятия форме, мы используем строковую переменную, в котороую последовательно записываются элементы массива, отделенные друг от друга запятой. Здесь, в качестве управляющей переменной, выступает итератор списка lst, который создается в выражении-инициализаторе как локальная переменная цикла.
[править] Цикл foreach
В некоторых языках программирования присутствует специальная синтаксическая конструкция цикла foreach. Как правило, она применяется в случаях, когда необходимо перебрать все значения некоторой коллекции и выполнить код, дословно, "для каждого из значений".
Учитывая то, что язык K++ ориентирован на использование блоков, непосредственная надобность в такой конструкции отпадает, поскольку ее функционал с успехом может быть реализован и даже превзойден стандартными методами коллекций, такими как each(), each_pair() и each_with_index(). Первый метод делает ровно то же самое что и традиционная конструкция foreach, второй вызывает блок, передавая ему пару из индекса и соответствующему индексу элемента из коллекции. Третий метод передает только сам индекс.
Тем не менее, в целях удобства программистов, была создана системная функция, с названием foreach(), которая ведет себя подобно оператору. На деле она всего лишь вызывает метод each() у передаваемой ей коллекции: <source lang="kpp"> function void foreach(const collection, block b) {
collection.each(b);
} </source>
Как видите, в качестве первого аргумента передается динамическая переменная, которая используется для вызова соответствующего метода. В целом, рекомендуется использовать прямой вызов метода, тем более что он является более эффективным, и позволяет писать составные конструкции прямо в одном выражении (см. примеры в главе Блоки).
Примечание: поскольку, с точки зрения языка К++, foreach не является синтаксической конструкцией, ее следует вызывать подобно функции. Это значит, что после закрывающей фигурной скобки должна стоять точка с запятой: <source lang="kpp"> var ary = [1, 2, 3]; var p = 1; foreach (ary) { |x|
p *= x;
}; </source>
[править] Управление циклами
Как уже было сказано ранее, в некоторых случаях может потребоваться изменить поведение циклов по умолчанию. Это реализуется с помощью ключевых слов break и continue.
Ключевое слво break применяется для принудительного завершения выполнения цикла и передачи управления коду, следующему за циклом. Напротив, ключевое слво continue применяется для прерывания текущей итерации и мгновенного перехода к началу следующей. В случае цикла while, это просто будет означать переход на код проверки условия; в случае цикла for — переход на код приращения управляющей переменной, с последующим переходом на код проверки условия.
Например, цикл for по умолчанию является циклом с предпроверкой условия. То есть, сначала проверяется условие, а затем либо выполняется очередная итерация, либо управление переходит за пределы цикла. Чтобы сделать из него цикл с постпроверкой условия, достаточно вынести код проверки условия в конец тела цикла: <source lang="kpp"> for (/*инициализатор*/; ; /*приращение*/) {
/* тело цикла */ if (/*условие*/) break;
} </source>
Теперь, тело цикла будет выполнено как минимум один раз, после чего будет проверено условие. По умолчанию, пустое выражение условия (в заголовке цикла) означает истину. То есть, цикл без граничных условий будет выполняться вечно, что подводит нас к идее бесконечных циклов:
[править] Понятие бесконечного цикла
В некоторых случаях бывает необходимо выполнять определенные, повторяющиеся действия большое количество раз, причем, заранее неизвестно, сколько итераций необходимо выполнить. Таких задач в программировании довольно много. Как правило это различные сервисные алгоритмы, например такие, как коды обработки входящих клиентских подключений, различные системы контроля и т. д. В них существует как минимум одно место которое должно выполняться в течение всего времени, пока работает программа.
Для реализации подобных алгоритмов, применяют бесконечные циклы, которые образуются из обычных, конечных путем изменения условия их выполнения: <source lang="kpp"> //бесконечный цикл while: while (true) {
/* тело бесконечного цикла */
}
//бесконечный цикл for: for (;;) {
/* тело бесконечного цикла */
} </source>
В первом случае, мы видим, что выражение условия цикла while представляет собой булеву константу true. Разумеется, ее значение постоянно и соответствует истине. Поскольку цикл while повторяется до тех пор, пока значение условного выражения истинно, получается, что этот цикл будет выполняться бесконечно.
То же самое происходит и с циклом for. Как уже было сказано выше, пустое выражение условия цикла соответствует истине, поэтому этот цикл тоже никогда не завершится.
Разумеется, подлинно бесконечные циклы никому не нужны. Компьютеры хоть редко, но все же выключаются, например для обновления программного обеспечения, или улучшения материальной базы. Соответственно программы должны иметь возможность "аккуратного" выключения своими силами. Для этого, в тело бесконечных циклов добавляется код, проверяющий некоторое внешнее условие, которое становится истинным тогда, когда требуется завершить выполнение, и прерывающий цикл с помощью ключевого слова break.
Примечание: при разработке закольцованных алгоритмов следует быть очень осторожным и необходимо учитывать их особенности. Вот некоторые соображения, которые помогут вам избежать ошибок в реализации программ:
- Действительно ли требуется бесконечный цикл? Возможно задача может быть решена обычным, итеративным образом.
- Реализован ли корректный код прерывания цикла? Изменяется ли где-то проверочное условие? Бывает, что программисты либо забывают реализовать код прерывания цикла, либо забывают установить условие прерывания где то в другой части программы, что в конечном счете, приводит к неуправляемым программам.
- Насколько "прожорлив" код, выполняющийся в теле цикла? Может получиться так, что код находящийся в теле цикла, будет захватывать все доступные вычислительные ресурсы системы, поскольку не ограничен в потребностях. В таком случае, вся система станет нестабильной и будет плохо реагировать на действия пользователя. Чтобы этого избежать, необходимо реализовывать дополнительные механизмы, вроде механизма событий (код ожидает некоторое событие после чего продолжает работу), либо вставлять задержку, достаточную, чтобы другие процессы могли своевременно выполняться.
[править] Взаимозаменяемость циклических структур
Как вы уже наверное заметили, одни и те же задачи в языке К++, так же как и в многих других языках, могут быть решены несколькими способами. Это дает большую свободу программисту и способность решать задачи наиболее подходящим и удобным в данный момент образом.
То же самое относится и к циклам. Фактически, низкоуровневая реализация любого цикла сводится к операциям вычисления значений и соответствующим переходам. В этом смысле, цикл for отличается от цикла while только тем, что в его синтаксисе уже заранее предусмотрены некоторые типовые операции, присущие большинству циклов.
Приведем примеры, показывающие взаимозаменяемость циклов:
<source lang=kpp>
var s = 0; for (var i = 0; i < 10; ++i) s += i;
myObject.Compact(); //бесконечный цикл while while (true) { var need_stop = ProcessEvents(); if (need_stop) break; } </source> |
<source lang=kpp>
//цикл while в роли цикла for: var i = 0; var s = 0; while (i < 10) s += i++; //цикл for в роли цикла while var myObject = new MyClass; for ( ; !myObject.Compacted; ) myObject.Compact(); //бесконечный цикл for for (;;) { var need_stop = ProcessEvents(); if (need_stop) break; } </source> |
Мы видим, что один и тот же алгоритм можно реализовать как с помощью цикла while, так и с помощью for. Конечно, некоторые из указанных примеров выглядят, мягко говоря, странно. Это и понятно, — каждый из циклов создавался для решения определенных задач и был ориентирован именно на них. Цикл for создавался для задач, в которых присутствует управляющая переменнаяя, в то время как цикл while — для задач общей логики. Какой из циклов применять в каждом случае, и какой из них в этом случае является удобнее, должен решать сам программист.