PVOID - Управляющие структуры

Управляющие структуры

Этот раздел является переводом туториала C++ Language


Выражения и управление потоком


   Простое выражение в C++ - это каждая из отдельных инструкций программы, таких как объявление переменной или одно из выражений, рассмотренных в предыдущих разделах. Они всегда завершаются точкой с запятой (;) и выполняются в том же порядке, в котором они представлены в программе.

   Но программы не ограничиваются линейной последовательностью выражений. Во время выполнения программа может повторять сегменты кода или принимать решения, и разветвляться. Для этих целей C++ предоставляет выражения для управления потоком исполнения, которые служат для указания, что наша программа должна сделать, когда и при каких обстоятельствах.

   Многие из выражений управления потоком, освещенных в этом разделе требуют определенных (под)выражений как части синтаксиса. Эти выражения могут быть простыми выражениями C++, такими как одиночная инструкция, отделенная точкой с запятой (;), или составными выражениями. Составное выражение это группа выражений (каждое их них отделяется точкой с запятой), сгруппированных вместе в блок, заключенный в фигурные скобки: {}:

{ statement1; statement2; statement3; }

   Весь блок рассматривается как одно выражение (состоящее из множества подвыражений). Выражение, являющееся частью синтаксиса выражения управления потоком, может быть как простым, так и составным.

 

Операторы выбора: if и else

   Ключевое слово if используется для выполнения оператора или блока, если и только если выполняется условие. Его синтаксис:

if (condition) statement;

   Здесь condition является вычисляемым выражением. Если condition равно true, выполняется выражение statement. Если же оно равно false, то statement не выполняется (просто игнорируется), и программа продолжает выполнение сразу после условного оператора. Например, следующий фрагмент кода печатает сообщение (x is 100), только если значение, хранимое в переменной x, действительно 100:

if (x == 100)
  cout << "x is 100";

   Если x не равен точно 100, это выражение игнорируется, и ничего не печатается.

 Если необходимо исполнить более одного выражения при выполнении условия, эти выражения должны быть заключены в фигурные скобки ({}), формируя блок:

if (x == 100)
{
   cout << "x is ";
   cout << x;
}

Обычно отступы и переносы строки в коде не имеют никакого эффекта, так что этот код эквивалентен следующему:

if (x == 100) { cout << "x is "; cout << x; }

   Операторы выбора с if также могут указывать, что происходит, когда условие не выполняется, используя ключевое слово else для введения альтернативного выражения. Его синтаксис:

if (condition) statement1 else statement2;

 где statement1 выполняется в случае, если условие имеет значение true, а в случае, если это не так, выполняется statement2.

 Например:

if (x == 100)
  cout << "x is 100";
else
  cout << "x is not 100";

   Этот фрагмент напечатает x is 100, если x действительно имеет значение 100, но если это не так (и только в этом случае), этот фрагмент кода напечатает x is not 100. Несколько структур if + else могут быть объединены с целью проверки диапазона значений.

 Например:

if (x > 0)
  cout << "x is positive";
else if (x < 0)
  cout << "x is negative";
else
  cout << "x is 0";

   Этот фрагмент напечатает x положителен, отрицателен или равен 0 путем объединения двух структур if-else. Также возможно выполнить более одного выражения на каждый случай, группируя их в блоки, заключенные в фигурные скобки: {}.

 

Итерационные операторы (циклы)

  Циклы повторяю выражение определенное количество раз, или до тех пор, пока не будет выполнено условие. Они представлены ключевыми словами while, do и for.

 

Цикл while

  Простейшая разновидность циклов это цикл while. Его синтаксис:

 while (expression) statement

  Цикл while просто повторяет выражение statement до тех пор, пока выражение expression истинно. Если после какого-либо выполнения выражения statement выражение expression станет ложно, цикл завершится, и программа продожит исполнение непосредственно после цикла. Например, рассмотрим обратный отсчет с использованием цикла while:

// пользовательский обратный отсчет
// с использованием while

#include <iostream>
using namespace std;

int main ()
{
  int n = 10;

  while (n>0) {
    cout << n << ", ";
    --n;
  }

  cout << "liftoff!\n";
}
10, 9, 8, 7, 6, 5, 4, 3, 2, 1, liftoff!

Первое выражение в main устанавливает n в значение 10. Это первое число для счетчика. Затем начинается цикл while: если это значение удовлетворяет условию n>0 (т.е. n больше нуля), то блок, который следует за условием, выполняется, и повторяется, пока условие (n>0) остается истинно.


 Весь процесс этой программы может быть интерпретирован, согласно следующему скрипту (начиная с main):

  1. Присваивается значение n

  2. Проверяется условие цикла while (n>0). В этом месте есть два пути:

    • условие истинно: выражение исполняется (шаг 3)

    • условие ложно: выражение игнорируется и выполнение продолжается после него (шаг 5)

  3. Выполнение выражения:

    cout << n << ", ";
    --n;

    (печатает значение n и уменьшает n на 1)

  4. Конец блока. Автоматический возврат к шагу 2.

  5. Продолжение программы сразу после блока:

    печать liftoff! и конец программы.

   При использовании цикла while следует учитывать, что цикл должен заканчиваться в некоторой точке, и поэтому выражение должно изменять каким-то образом значения, проверяемые в условии, чтобы сделать условие ложным в какой-то момент. Иначе, цикл будет продолжать повторяться бесконечно. В нашем случае, цикл содержит --n, что уменьшает значение переменной, которая проверяется в условии (n), на единицу, что в конечном счете делает условие (n>0) ложным, после определенного числа итераций цикла. Чтобы быть более конкретным, после 10 итераций, n будет иметь значение 0, что делает условие ложным и завершает цикл while.

 Обратите внимание, что сложность этого цикла незначительна для компьютера, и поэтому весь обратный отсчет выполняется мгновенно, без какой-либо существенной задержки между итерациями цикла (если интересно, см. sleep_for: пример обратного отсчета с задержками).

 

Цикл do-while

  Очень похожий цикл - цикл do-while, синтаксис которого:

 do statement while (condition);

  Он ведет себя как цикл while, за исключением того, что условие condition вычисляется после выполнения выражения statement, а не перед ним, гарантируя хотя бы однократное выполнение выражения statement, даже если условие condition никогда не выполнится. Например, следующая программа-пример повторяет любой текст, который вводит пользователь, пока пользователь не введет goodbye:

// echo machine
#include <iostream>
#include <string>
using namespace std;

int main ()
{
  string str;
  do {
    cout << «Enter text: »;
    getline (cin,str);
    cout << «You entered: » << str << ’\n’;
  } while (str != «goodbye»);
}
Enter text: hello
You entered: hello
Enter text: who’s there?
You entered: who’s there?
Enter text: goodbye
You entered: goodbye

  Цикл do-while обычно предпочтительнее цикла while, когда выражение statement должно быть выполнено хотя бы один раз, например, когда условие, которое проверяется до конца цикла, определяется внутри самого оператора цикла. В предыдущем примере пользовательский ввод в блоке определяет, завершится ли цикл. И, таким образом, даже если пользователь хочет завершить цикл как можно скорее, введя goodbye, блок в цикле должен быть выполнен по крайней мере один раз, чтобы запросить ввод, и условие может быть определено только после выполнения блока.

Цикл for

  Цикл for создан для повторения определенного количества раз. Его синтаксис:

 for (initialization; condition; increase) statement;

  Подобно циклу while, этот цикл повторяет выражение statement до тех пор, пока условие condition истинно. Но, в дополнение, цикл for предоставляет конкретные места для инициализации и прироста (increase) выражения, выполняемого перед тем, как цикл произведет первую итерацию, и после каждой итерации, соответственно. Поэтому особенно полезно использовать переменные счетчика в качестве условия condition.

 Это работает следующим образом:

  1. выполняется инициализация. Обычно здесь объявляется переменная-счетчик, и ей задается какое-то начальное значение. Это выполняется единственный раз, в начале цикла.

  2. проверяется условие condition. Если оно истинно, цикл продолжается; иначе, цикл заканчивается, пропуская выражение statement и переходя к шагу 5.

  3. выполняется выражение statement. Как обычно, оно может быть как единичным оператором, так и блоком операторов, заключенных в фигурные скобки {}.

  4. выполняется прирост increase, и цикл возвращается к шагу 2.

  5. цикл заканчивается: выполнение программы продолжается с опреатора, следующего за циклом.

 Вот пример обратного отсчета с использованием цикла for:

// обратный отсчет 
// с использованием
// цикла for

#include <iostream>
using namespace std;

int main ()
{
  for (int n=10; n>0; n--) {
    cout << n << ", ";
  }
  cout << "liftoff!\n";
}
10, 9, 8, 7, 6, 5, 4, 3, 2, 1, liftoff!

  Три поля в цикле for являются опциональныи. Они могут быть оставлены пустыми, но в любом случае требуются точки с запятой между ними. Например, for (;n<10;) - это цикл без инициализации или приращения (эквивалент цикла while); for (;n<10;++n) - это цикл с приращением, но без инициализации (возможно, потому что переменная уже была инициализирована перед циклом). Цикл без условия condition является эквивалентом цикла с true вместо условия (т.е. бесконечного цикла).

   Поскольку каждое из полей выполняется в определенный момент исполнения цикла, может быть полезно выполнить более одного выражения на месте любого из полей initialization, condition, или statement. К сожалению, это не операторы, а простые выражения, и поэтому их нельзя заменить блоком. Однако в качестве выражений они могут использовать оператор запятая (,): этот оператор является разделителем выражений и может разделять несколько выражений, где обычно ожидается только одно. Например, используя его, цикл for мог бы обрабатывать две переменные счетчика, инициализируя и увеличивая обе:

for ( n=0, i=100 ; n!=i ; ++n, --i )
{
   // здесь что угодно...
}

 Этот цикл будет выполнен 50 раз, если ни n, ни i не будут изменены в цикле: 

for loop

  n начинается со значения 0, а i со 100, условие n! = (то есть, что n не равно i). Поскольку n увеличивается на единицу, а i уменьшается на единицу в каждой итерации, условие цикла станет ложным после 50-й итерации, когда n и i станут равны 50.

 

Range-based for. Цикл на основе диапазона.

  Цикл for имеет другой синтаксис, который используется исключительно с диапазонами значений:

 for ( declaration : range ) statement;

  Этот вид цикла for перебирает все элементы в диапазоне range, где declaration объявляет некоторую пременную, способную принимать значение элемента этого диапазона. Диапазоны — это последовательности элементов, включая массивы, контейнеры и любой другой тип, поддерживающий функции begin и end; большинство из этих типов еще не были представлены в этом руководстве, но мы уже знакомы по крайней мере с одним видом диапазона: строками, которые являются последовательностями символов.

Пример такого цикла с использованием строк:

// range-based for
#include <iostream>
#include <string>
using namespace std;

int main ()
{
  string str {"Hello!"};
  for (char c : str)
  {
    cout << "[" << c << "]";
  }
  cout << '\n';
}
[H][e][l][l][o][!]

Обратите внимание, что перед двоеточием (:) в цикле for находится объявление переменной типа char (элементы в строке имеют тип char). Затем мы используем эту переменную, c, в блоке операторов для представления значения каждого из элементов в диапазоне.

 Этот цикл является автоматическим и не требует явного объявления какой-либо переменной-счетчика.

 Циклы на основе диапазона обычно также используют выведение типов для типов элементов с auto. Как правило, вышеприведенный цикл на основе диапазона также может быть записан как:

for (auto c : str)
  cout << "[" << c << "]";

 Здесь тип переменной c автоматически определяется как тип элементов, содержащихся в str.

 

Операторы перехода (jump)

  Операторы перехода позволяют изменять течение программы, выполняя переходы в определенные места.

 

Оператор break

 break завершает цикл, даже если условие его завершения не выполнено. Его можно использовать для завершения бесконечного цикла или для принудительного завершения цикла до его нормального завершения. Например, давайте остановим обратный отсчет до его нормального завершения:

// пример завершения цикла
#include <iostream>
using namespace std;

int main ()
{
  for (int n=10; n>0; n--)
  {
    cout << n << ", ";
    if (n==3)
    {
      cout << "countdown aborted!";
      break;
    }
  }
}
10, 9, 8, 7, 6, 5, 4, 3, countdown aborted!

 

Оператор continue

  Оператор continue заставляет программу пропустить остаток цикла в текущей итерации, как будто достигнут конец блока операторов, перейдя к началу следующей итерации. Например, давайте пропустим номер 5 в нашем обратном отсчете:

 

Оператор goto

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

  Точка назначения идентифицируется меткой (label), которая затем используется в качестве аргумента для оператора goto. Метка состоит из корректного идентификатора, за которым следует двоеточие (:).

 goto, как правило, считается низкоуровневой операцией без каких-либо случаев использования в современных парадигмах программирования более высокого уровня, обычно используемых в C++. Но, в качестве примера, вот версия нашего цикла обратного отсчета с использованием goto:

// пример цикла
// с оператором continue

#include <iostream>
using namespace std;

int main ()
{
  for (int n=10; n>0; n--) {
    if (n==5) continue;
    cout << n << ", ";
  }
  cout << "liftoff!\n";
}
10, 9, 8, 7, 6, 4, 3, 2, 1, liftoff!

 

Оператор выбора: switch

Синтаксис оператора switch немного своеобразен. Его целью является проверка значения среди ряда возможных константных выражений. Это похоже на объединение операторов if-else, но ограничено константными выражениями. Его наиболее типичный синтаксис:

switch (expression)
{
case constant1:
  group-of-statements-1;
  break;
case constant2:
  group-of-statements-2;
  break;
.
.
.
default:
  default-group-of-statements
}

  Это работает следующим образом: switch вычисляет выражение expression и проверяет, эквивалентно ли оно значению constant1; если это так, выполняет group-of-statements-1, пока не найдет оператор break. Когда он находит оператор break, программа переходит к концу всего оператора switch (закрывающая фигурная скобка).

 Если выражение не равно constant1, тогда проверяется constant2. Если выражение равно constant2, выполняется group-of-statements-2, до тех пор, пока не будет найден оператор break, после чего будет совершен переход к концу оператора switch.

 Наконец, если значение выражения не соответствует ни одной из ранее указанных констант (их может быть любое количество), программа выполняет операторы, включенные после метки default:, если она существует (так как она необязательна).

 Оба следующих фрагмента кода имеют одинаковое поведение, демонстрируя эквивалентность операторов if-else и switch:

 switch example   if-else equivalent 
 switch (x) {
 case 1:
   cout << "x is 1";
   break;
 case 2:
   cout << "x is 2";
   break;
 default:
   cout << "value of x unknown"; 
 }
 if (x == 1) {
   cout << "x is 1";
 }
 else if (x == 2) {
   cout << "x is 2";
 }
 else {
   cout << "value of x unknown"; 
 }

  Оператор switch имеет несколько своеобразный синтаксис, унаследованный с ранних времен первых компиляторов Си, потому что он использует метки вместо блоков. В наиболее типичном использовании (показано выше), это означает, что операторы break необходимы после каждой группы операторов для определенной метки. Если break отсутствует, все операторы, следующие за case (включая те, которые находятся под любыми другими метками) также выполняются до тех пор, пока не будет достигнут конец блока switch или оператор перехода (такой как break).

  Если в приведенном выше примере будет отсутствовать оператор break после первой группы для первого случая, программа не будет автоматически переходить к концу блока switch после того, как напечатает x is 1, а вместо этого начнет выполнять операторы для case 2 (т.е. напечатает также x is 2). Это будет продолжаться до тех пор, пока не встретится оператор break или конец блока switch. Это делает излишним заключать операторы для каждого случая в фигурные скобки {}, а также может быть полезно для выполнения одной и той же группы операторов для разных возможных значений. Например:

switch (x) {
  case 1:
  case 2:
  case 3:
    cout << "x is 1, 2 or 3";
    break;
  default:
    cout << "x is not 1, 2 nor 3";
  }

  Обратите внимание, что оператор switch ограничен в сравнении оцениваемого выражения метками. являющимися константными выражениями. В качестве меток невозможно использовать переменные или диапазоны значений, потому что они не являются допустимыми константами C++.

Для проверки диапазонов или значений, не являющихся константами, лучше использовать группу операторов if и else if.