Радиоконструктор

Язык C, Часть 19

Лестница if-else-if

В программах часто используется конструкция, которую называют лестницей if-else-if[1]. Общая форма лестницы имеет вид
if (выражение) оператор;
else
  if (выражение) оператор;
  else
    if (выражение) оператор;
    .
    .
    .
    else оператор
 
 
Работает эта конструкция следующим образом. Условные выражения операторов if вычисляются сверху вниз. После выполнения некоторого условия, т.е. когда встретится выражение, принимающее значение ИСТИНА, выполняется ассоциированный с этим выражением оператор, а оставшаяся часть лестницы пропускается. Если все условия ложны, то выполняется оператор в последней фразе else, а если последняя фраза else отсутствует, то в этом случае не выполняется ни один оператор.
Недостаток предыдущей записи лестницы состоит в том, что с ростом глубины вложенности увеличивается количество отступов в строке. Это становится неудобным с технической точки зрения. Поэтому лестницу if-else-if обычно записывают так:
if (выражение)
  оператор;
else if (выражение)
  оператор;
else if (выражение)
  оператор;
    .
    .
    .
else
  оператор; 
Используя лестницу if-else-if, программу для игры в "магическое число" можно записать так:
/* Магическое число, программа №4. */
#include <stdio.h>
#include <stdlib.h>

int main(void) 
{
  int magic; /* магическое число */
  int guess; /* попытка игрока */

  magic = rand(); /* генерация магического числа */

  printf("Угадай магическое число: ");
  scanf("%d", &guess);

  if(guess == magic) {
    printf("** Верно ** ");
    printf("Магическое число равно %d\n", magic);
  }
  else if(guess > magic)
    printf("Неверно, слишкое большое");
  else printf("Неверно, слишком малое");

  return 0;
}

Оператор "?", альтернативный условному

Оператор ? можно использовать вместо оператора if-else, записанного в форме
if (условие) переменная = выражение;
else переменная = выражение;
Оператор ? является тернарным, потому что он имеет три операнда. Его общая форма следующая:
Выражение1 ? Выражение2 : Выражение3;
Обратите внимание на использование и расположение двоеточия.
Результат операции ? определяется следующим образом. Сначала вычисляется Выражение1. Если оно имеет значение ИСТИНА, вычисляется Выражение2 и его значение становится результатом операции ?. Если Выражение1 имеет значение ЛОЖЬ, вычисляется Выражение3 и его значение становится результатом операции ?. Например:
x = 10; 
y = x>9 ? 100 : 200;
В этом примере переменной y присваивается значение 100. Если бы x было меньше 9, то переменная у получила бы значение 200. То же самое можно записать, используя оператор if-else:
x = 10;
if(x>9) y = 100;
else y = 200;
В следующем примере оператор ? используется для присвоения квадрату числа знака числа. (Само число вводится пользователем.) В этой программе при возведении в квадрат фактически сохраняется знак числа. Например, если пользователь введет 10, это число будет возведено в квадрат и в результате программа напечатает 100, а если пользователь введет число -10, то оно будет возведено в квадрат и результату будет приписан знак числа; в этом случае будет напечатано -100.
#include <stdio.h>

int main(void)
{
  int isqrd, i;

  printf("Введите число: ");
  scanf("%d", &i);

  isqrd = i>0 ? i*i : -(i*i);

  printf("%d число в квадрате %d", i, isqrd);

  return 0;
}
(Обратите внимание, что в результате выполнения данной программы могут быть напечатаны не только верные утверждения. Не всегда компьютеры печатают только правильные результаты, если даже они работают без сбоев!)
Оператор ? можно использовать вместо if-else не только в операторе присваивания. Как известно, все функции (за исключением имеющих тип результата void) возвращают значение. Следовательно, в операторе ? можно использовать вызовы функций. Когда в выражении встречается вызов функции, она выполняется, а возвращаемое ею значение используется при вычислении выражения. Это значит, что можно выполнить одну или несколько функций путем размещения их вызовов в выражениях оператора ? в качестве операндов. Например:
#include <stdio.h>

int f1(int n);
int f2(void);

int main(void)
{
  int t;

  printf("Введите число: ");
  scanf("%d", &t);

  /* печать соответствующего сообщения */
  t ? f1(t) + f2() : printf("Введен нуль.");
  printf("\n");

  return 0;
}

int f1(int n)
{
  printf("%d ", n);
  return 0;
}

int f2(void)
{
  printf(" введено ");
  return 0;
}
Эта программа сначала запрашивает число. При вводе нуля вызывается функция printf(), выводящая на экран сообщение введен нуль. При вводе отличного от нуля числа выполняются как f1(), так и f2(). Обратите внимание на то, что значение выражения ? в этом примере не присваивается никакой переменной, оно просто отбрасывается.
Следует помнить, что компилятор, пытаясь оптимизировать объектный код, может установить любой порядок вычисления значений операндов. В данном примере это значит, что функции f1() и f2() выполняются в произвольном порядке и сообщение введено может появиться как до, так и после числа.
Используя оператор ?, программу для игры в "магическое число" можно переписать следующим образом:
/* Магическое число, программа №5. */
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
  int magic; /* магическое число */
  int guess; /* попытка игрока */

  magic = rand(); /* генерация магического числа */

  printf("Угадай магическое число: ");
  scanf("%d", &guess);

  if(guess == magic) {
    printf("** Верно ** ");
    printf("Магическое число равно %d\n", magic);
  }
  else
    guess > magic ? printf("Слишком большое") :
                       printf("Слишком малое");

  return 0;
} 
В этой программе оператор ? печатает соответствующее сообщение на основе проверки условия guess > magic.

Условное выражение

У начинающих программистов иногда возникают трудности в связи с тем, что в условном (управляющем) выражении операторов if или ? могут стоять любые операторы, причем это не обязательно операторы отношения или логические (как в языках Basic или Pascal). В языке С значением результата управляющего выражения являются ИСТИНА или ЛОЖЬ, однако тип результата может быть любым скалярным типом. Считается, что любой ненулевой результат представляет значение ИСТИНА, а нулевой — ЛОЖЬ.
В следующем примере программа считывает с клавиатуры два числа, вычисляет их отношение и выводит его на экран. Оператор if используется для того, чтобы избежать деления на нуль, если второе число равно нулю.
/* Деление первого числа на второе. */

#include <stdio.h>

int main(void)
{
  int a, b;

  printf("Введите два числа: ");
  scanf("%d%d", &a, &b);

  if(b) printf("%d\n", a/b);
  else printf("Делить на нуль нельзя.\n");

  return 0;
}
Если управляющее выражение b равно 0, то его результат представляет значение ЛОЖЬ и выполняется оператор else. В противном случае (b не равно нулю) результат представляет значение ИСТИНА и выполняется деление чисел.
В последнем примере оператор if можно записать так:
if(b != 0) printf("%d\n", a/b);
Но следует отметить, что такая форма записи избыточна, она может привести к генерации неоптимального кода, кроме того, это считается признаком плохого стиля. Переменная b сама по себе представляет значение ИСТИНА или ЛОЖЬ, поэтому сравнивать ее с нулем нет необходимости.

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

Оператор выбора switch (часто его называют переключателем) предназначен для выбора ветви вычислительного процесса исходя из значения управляющего выражения. (При этом значение управляющего выражения сравнивается со значениями в списке целых или символьных констант. Если будет найдено совпадение, то выполнится ассоциированный с совпавшей константой оператор.) Общая форма оператора switch следующая:
switch (выражение) {
  case постоянная1:
    последовательность операторов
    break;
  case постоянная2:
    последовательность операторов
    break;
  case постоянная3:
    последовательность операторов
    break;
  default:
    последовательность операторов;
}
Значение выражения оператора switch должно быть таким, чтобы его можно было выразить целым числом. Это означает, что в управляющем выражении можно использовать переменные целого или символьного типа, но только не с плавающей точкой. Значение управляющего выражения по очереди сравнивается с постоянными в операторах case. Если значение управляющего выражения совпадет с какой-то из постоянных, управление передается на соответствующую метку case и выполняется последовательность операторов до оператора break. Если оператор break отсутствует, выполнение последовательности операторов продолжается до тех пор, пока не встретится break (в другой метке) или не кончится тело оператора switch (т.е. блок, следующий за switch). Оператор default выполняется в том случае, когда значение управляющего выражения не совпало ни с одной постоянной. Оператор default также может отсутствовать. В этом случае при отсутствии совпадений не выполняется ни один оператор.
Согласно Стандарту С89, оператор switch может иметь как минимум 257 операторов case. Стандарт С99 требует поддержки как минимум 1023 операторов case. Ели вы пишете программы вручную, такое большое количество операторов вам никогда не понадобится[2]. Оператор case — это метка, однако он не может быть использован сам по себе, вне оператора switch.
Оператор break — это один из операторов безусловного перехода. Он может применяться не только в операторе switch, но и в циклах, (см. раздел "Операторы цикла"). Когда в теле оператора switch встречается оператор break, программа выходит из оператора switch и выполняет оператор, следующий за фигурной скобкой } оператора switch.
Об операторе switch очень важно помнить следующее:
  • Оператор switch отличается от if тем, что в нем управляющее выражение проверяется только на равенство с постоянными, в то время как в if проверя ется любой вид отношения или логического выражения.
  • В одном и том же операторе switch никакие два оператора case не могут иметь равных постоянных. Конечно, если один switch вложен в другой, в их операторах case могут быть совпадающие постоянные.
  • Если в управляющем выражении оператора switch встречаются символьные константы, они автоматически преобразуются к целому типу по принятым в языке С правилам приведения типов.
Оператор switch часто используется для обработки команд с клавиатуры, например, при выборе пунктов меню. В следующем примере программа выводит на экран меню проверки правописания и вызывает соответствующую процедуру:
void menu(void)
{
  char ch;

  printf("1. Проверка правописания\n");
  printf("2. Коррекция ошибок\n");
  printf("3. Вывод ошибок\n");
  printf("Для пропуска нажмите любую клавишу\n");
  printf("      Введите Ваш выбор: ");

  ch = getchar(); /* чтение клавиш */

  switch(ch) {
    case '1':
      check_spelling();
      break;
    case '2':
      correct_errors();
      break;
    case '3':
      display_errors();
      break;
    default :
      printf("Ни выбрана ниодна опция");
  }
} 
С точки зрения синтаксиса, присутствие операторов break внутри switch не обязательно. Они прерывают выполнение последовательности операторов, ассоциированных с данной константой. Если оператор break отсутствует, то выполняется следующий оператор case, пока не встретится очередной break, или не будет достигнут конец тела оператора switch. Например, в функции inp_handler() (обработчик ввода драйвера) для упрощения программы несколько операторов break опущено, поэтому выполняются сразу несколько операторов case:
/* Обработка значения i */
void inp_handler(int i)
{
  int flag; 

  flag = -1; 

  switch(i) {
    case 1:  /* Эти case эти общую */
    case 2:  /* последовательность операторов. */
    case 3:
      flag = 0;
      break;
    case 4:
      flag = 1;
    case 5:
      error(flag);
      break;
    default:
      process(i);
  }
}
Приведенный пример иллюстрирует следующие две особенности оператора switch().
Во-первых, оператор case может не иметь ассоциированной с ним последовательности операторов. Тогда управление переходит к следующему case. В этом примере три первых case вызывают выполнение одной и той же последовательности операторов, а именно:
flag = 0;
break;
Во-вторых, если оператор break отсутствует, то выполняется последовательность операторов следующего case. Если i равно 4, то переменной flag присваивается значение 1 и, поскольку break отсутствует, выполнение продолжается и вызывается error(flag). Если i равно 5, то error() будет вызвана со значением переменной flag, равным —1, а не 1.
То, что при отсутствии break операторы case выполняются вместе, позволяет избежать ненужного дублирования операторов[3].

Вложенные операторы switch

Оператор switch может находиться в теле внешнего по отношению к нему оператора switch. Операторы case внутреннего и внешнего switch могут иметь одинаковые константы, в этом случае они не конфликтуют между собой. Например, следующий фрагмент программы вполне работоспособен:
switch(x) {
  case 1:
    switch(y) {
      case 0: printf("Деление на нуль.\n");
              break;
      case 1: process(x,y);
              break;
    }
    break;
  case 2:
    .
    .
    .


[1]Называется также структурой выбора или конструкцией условного перехода.
[2]Если же для генерации программ вы используете макрогенераторы или генераторы компиляторов, например, уасс или lex, то на данное ограничение следует обратить внимание.
[3]Но представляет собой опасность для забывчивых программистов.

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

В языке С, как и в других языках программирования, операторы цикла служат для многократного выполнения последовательности операторов до тех пор, пока выполняется некоторое условие. Условие может быть установленным заранее (как в операторе for) или меняться при выполнении тела цикла (как в while или do-while).

Цикл for

Во всех процедурных языках программирования циклы for очень похожи. Однако в С этот цикл особенно гибкий и мощный. Общая форма оператора for следующая:
for (инициализация; условие; приращение) оператор;
Цикл for может иметь большое количество вариаций. В наиболее общем виде принцип его работы следующий. Инициализация — это присваивание начального значения переменной, которая называется параметром цикла. Условие представляет собой условное выражение, определяющее, следует ли выполнять оператор цикла (часто его называют телом цикла) в очередной раз. Оператор приращение осуществляет изменение параметра цикла при каждой итерации. Эти три оператора (они называются также секциями оператора for) обязательно разделяются точкой с запятой. Цикл for выполняется, если выражение условие принимает значение ИСТИНА. Если оно хотя бы один раз примет значение ЛОЖЬ, то программа выходит из цикла и выполняется оператор, следующий за телом цикла for.
В следующем примере в цикле for выводятся на экран числа от 1 до 100:
#include <stdio.h>

int main(void)
{
  int x; 

  for(x=1; x <= 100; x++) printf("%d ", x);

  return 0;
} 
В этом примере параметр цикла х инициализирован числом 1, а затем при каждой итерации сравнивается с числом 100. Пока переменная х меньше 100, вызывается функция printf() и цикл повторяется. При этом х увеличивается на 1 и опять проверяется условие цикла х <= 100. Процесс повторяется, пока переменная х не станет больше 100. После этого процесс выходит из цикла, а управление передается оператору, следующему за ним. В этом примере параметром цикла является переменная х, при каждой итерации она изменяется и проверяется в секции условия цикла.
В следующем примере в цикле for выполняется блок операторов:
for(x=100; x != 65; x -= 5) {
  z = x*x;
  printf("Квадрат %d равен %d", x, z);
}
Операции возведения переменной х в квадрат и вызова функции printf() повторяются, пока х не примет значение 65. Обратите внимание на то, что здесь параметр цикла уменьшается, он инициализирован числом 100 и уменьшается на 5 при каждой итерации.
В операторе for условие цикла всегда проверяется перед началом итерации. Это значит, что операторы цикла могут не выполняться ни разу, если перед первой итерацией условие примет значение ЛОЖЬ. Например, в следующем фрагменте программы
x = 10;
for(y=10; y!=x; ++y) printf("%d", y);
printf("%d", y);  /* Это единственный printf()
                     который будет выполнен */
цикл не выполнится ни разу, потому что при входе в цикл значения переменных х и у равны. Поэтому условие цикла принимает значение ЛОЖЬ, а тело цикла и оператор приращение не выполняются. Переменная у остается равной 10, единственный результат работы этой программы — вывод на экран числа 10 в результате вызова функции printf(), расположенной вне цикла.

Варианты цикла for

В предыдущем разделе рассмотрена наиболее общая форма цикла for. Однако в языке С допускаются некоторые его варианты, позволяющие во многих случаях увеличить мощность и гибкость программы.
Один из распространенных способов усиления мощности цикла for — применение оператора "запятая" для создания двух параметров цикла. Оператор "запятая" связывает несколько выражений, заставляя их выполняться вместе (см. главу 2). В следующем примере обе переменные (х и у) являются параметрами цикла for и обе инициализируются в этом цикле:
for(x=0, y=0; x+y<10; ++x) {
  y = getchar();
  y = y - '0'; /* Вычитание из y ASCII-кода нуля */
    .
    .
    .
}
Здесь запятая разделяет два оператора инициализации. При каждой итерации значение переменной х увеличивается, а значение у вводится с клавиатуры. Для выполнения итерации как х, так и у должны иметь определенное значение. Несмотря на то что значение у вводится с клавиатуры, оно должно быть инициализировано таким образом, чтобы выполнилось условие цикла при первой итерации. Если у не инициализировать, то оно может случайно оказаться таким, что условие цикла примет значение ЛОЖЬ, тело цикла не будет выполнено ни разу.
Следующий пример демонстрирует использование двух параметров цикла. Функция converge() копирует содержимое одной строки в другую, начиная с обоих концов строки и кончая в ее середине.
/* Демонстрация использования 2-х параметров цикла. */
#include <stdio.h>
#include <string.h>

void converge(char *targ, char *src);

int main(void)
{
  char target[80] = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX";

  converge(target, "Это проверка функции converge().");
  printf("Строка-результат: %s\n", target);

  return 0;
} 

/* Эта функция копирует содержимое одной строки в
   другую, начиная с обоих концов и сходясь посередине. */
void converge(char *targ, char *src)
{
  int i, j; 

  printf("%s\n", targ);
  for(i=0, j=strlen(src); i<=j; i++, j--) {
    targ[i] = src[i];
    targ[j] = src[j];
    printf("%s\n", targ);
  }
}
Программа выводит на экран следующее:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
ЭXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
ЭтХХХХХХХХХХХХХХХХХХХХХХХХХХХХХ.
ЭтоХХХХХХХХХХХХХХХХХХХХХХХХХХХ).
Это ХХХХХХХХХХХХХХХХХХХХХХХХХ().
Это пХХХХХХХХХХХХХХХХХХХХХХХe().
Это прХХХХХХХХХХХХХХХХХХХХХge().
Это проХХХХХХХХХХХХХХХХХХХrge().
Это провХХХХХХХХХХХХХХХХХerge().
Это провeXXXXXXXXXXXXXXXverge().
Это провepXXXXXXXXXXXXXnverge().
Это провepKXXXXXXXXXXXonverge().
Это провepкaXXXXXXXXXconverge().
Это проверка ХХХХХХХ converge().
Это проверка фХХХХХи converge().
Это проверка фуХХХии converge().
Это проверка фунХции converge().
Это проверка функции converge().
Строка-результат: Это проверка функции converge().
В функции convergence() цикл for использует два параметра цикла (i и j) для индексации строки с противоположных концов. Параметр i в цикле увеличивается, а j — уменьшается. Итерации прекращаются, когда i становится больше j. Это обеспечивает копирование всех символов.
Проверка параметра цикла на соответствие некоторому условию не обязательна. Условие может быть любым логическим оператором или оператором отношения. Это значит, что условие выполнения цикла может состоять из нескольких условий, или операторов отношения. Следующий пример демонстрирует применение составного условия цикла для проверки пароля, вводимого пользователем. Пользователю предоставляются три попытки ввода пароля. Программа выходит из цикла, когда использованы все три попытки или когда введен верный пароль.
void sign_on(void)
{
  char str[20];
  int x;

  for(x=0; x<3 && strcmp(str, "password"); ++x) {
    printf("Пожалуйста, введите пароль:");
    gets(str);
  }

  if(x==3) return;
  /* Иначе пользователь допускается */
}
Функция sign_on() использует стандартную библиотечную функцию strcmp(), которая сравнивает две строки и возвращает 0, если они совпадают.
Следует помнить, что каждая из трех секций оператора for может быть любым синтаксически правильным выражением. Эти выражения не всегда каким-либо образом отображают назначение секции. Рассмотрим следующий пример:
#include <stdio.h>

int sqrnum(int num);
int readnum(void);
int prompt(void);

int main(void)
{
  int t;

  for(prompt(); t=readnum(); prompt())
    sqrnum(t);

  return 0;
} 

int prompt(void) 
{
  printf("Введите число: ");
  return 0;
} 

int readnum(void)
{
  int t;

  scanf("%d", &t);
  return t;
}

int sqrnum(int num)
{
  printf("%d\n", num*num);
  return num*num;
}
Здесь в main() каждая секция цикла for состоит из вызовов функций, которые предлагают пользователю ввести число и считывают его. Если пользователь ввел 0, то цикл прекращается, потому что тогда условие цикла принимает значение ЛОЖЬ. В противном случае число возводится в квадрат. Таким образом, в этом примере цикла for секции инициализации и приращения используются весьма необычно, но совершенно правильно.
Другая интересная особенность цикла for состоит в том, что его секции могут быть вообще пустыми, присутствие в них какого-либо выражения не обязательно. В следующем примере цикл выполняется, пока пользователь не введет число 123:
for(x=0; x!=123; ) scanf("%d", &x);
Секция приращения оператора for здесь оставлена пустой. Это значит, что перед каждой итерацией значение переменной х проверяется на неравенство числу 123, а приращения не происходит, оно здесь ненужно. Если с клавиатуры ввести число 123, то условие принимает значение ЛОЖЬ и программа выходит из цикла.
Инициализацию параметра цикла for можно сделать за пределами этого цикла, но, конечно, до него. Это особенно уместно, если начальное значение параметра цикла вычисляется достаточно сложно, например:
gets(s);  /* читает строку в s */
if(*s) x = strlen(s); /* вычисление длины строки */
else x = 10;

for( ; x<10; ) {
  printf("%d", x);
  ++x;
}
В этом примере секция инициализации оставлена пустой, а переменная х инициализируется до входа в цикл.

Бесконечный цикл

Для создания бесконечного цикла можно использовать любой оператор цикла, но чаще всего для этого выбирают оператор for. Так как в операторе for может отсутствовать любая секция, бесконечный цикл проще всего сделать, оставив пустыми все секции. Это хорошо показано в следующем примере:
for( ; ; ) printf("Этот цикл крутится бесконечно.\n");
Если условие цикла for отсутствует, то предполагается, что его значение — ИСТИНА. В оператор for можно добавить выражения инициализации и приращения, хотя обычно для создания бесконечного цикла используют конструкцию for( ; ; ).
Фактически конструкция for( ; ; ) не гарантирует бесконечность итераций, потому что в нем может встретиться оператор break, вызывающий немедленный выход из цикла. (Подробно оператор break рассмотрен в этой главе далее.) В этом случае выполнение программы продолжается с оператора, следующего за закрывающейся фигурной скобкой цикла for:
ch = '\0'; 

for( ; ; ) {
  ch = getchar(); /* считывание символа */
  if(ch=='A') break; /* выход из цикла */
} 

printf("Вы напечатали 'A'");
В данном примере цикл выполняется до тех пор, пока пользователь не введет с клавиатуры символ А.

Цикл for без тела цикла

Следует учесть, что оператор может быть пустым. Это значит, что тело цикла for (или любого другого цикла) также может быть пустым. Такую особенность цикла for можно использовать для упрощения некоторых программ, а также в циклах, предназначенных для того, чтобы отложить выполнение последующей части программы на некоторое время.
Программисту иногда приходится решать задачу удаления пробелов из входного потока. Допустим, программа, работающая с базой данных, обрабатывает запрос "показать все балансы меньше 400". База данных требует представления каждого слова отдельно, без пробелов, т.е. обработчик распознает слово "показать", но не " показать". В следующем примере цикл for удаляет начальные пробелы в строке str:
for( ; *str == ' '; str++) ;
В этом примере указатель str переставляется на первый символ, не являющийся пробелом. Цикл не имеет тела, так как в нем нет необходимости.[1]
Иногда возникает необходимость отложить выполнение последующей части программы на определенное время. Это можно сделать с помощью цикла for следующим образом:
for(t=0; t<SOME_VALUE; t++) ;
Единственное назначение этого цикла — задержка выполнения последующей части программы. Однако следует иметь в виду, что компилятор может оптимизировать объектный код таким образом, что пропустит этот цикл вообще, поскольку он не выполняет никаких действий, тогда желаемой задержки выполнения последующей части программы не произойдет.

Объявление переменных внутри цикла

В стандартах С99 и C++ (но не С89!) допускается объявление переменных в секции инициализации цикла for. Объявленная таким образом переменная является локальной переменной цикла и ее область действия распространяется на тело цикла.
Рассмотрим следующий пример:
/* 
    Здесь переменная i является локальной
    переменной цикла, а j видима вне цикла.

    *** Этот пример в C89 неправильный. ***
*/
int j; 
for(int i = 0; i<10; i++)
  j = i * i;

/* i = 10;
Это ошибка, переменная i здесь недоступна! */
В данном примере переменная i объявлена в секции инициализации цикла for и служит параметром цикла. Вне цикла переменная i невидима.
Поскольку параметр цикла чаше всего необходим только внутри цикла, его объявление в секции инициализации очень удобно и входит в широкую практику[2]. Однако необходимо помнить, что это не поддерживается стандартом С89.

Цикл while

Обшая форма цикла while имеет следующий вид:
while (условие) оператор;
Здесь оператор (тело цикла) может быть пустым оператором, единственным оператором или блоком. Условие (управляющее выражение) может быть любым допустимым в языке выражением. Условие считается истинным, если значение выражения не равно нулю, а оператор выполняется, если условие принимает значение ИСТИНА. Если условие принимает значение ЛОЖЬ, программа выходит из цикла и выполняется следующий за циклом оператор.
В следующем примере ввод с клавиатуры происходит до тех пор, пока пользователь не введет символ А:
char wait_for_char(void)
{
  char ch; 

  ch = '\0';  /* инициализация ch */
  while(ch != 'A') ch = getchar();
  return ch;
}
Переменная ch является локальной, ее значение при входе в функцию произвольно, поэтому сначала значение ch инициализируется нулем. Условие цикла while истинно, если ch не равно А. Поскольку ch инициализировано нулем, условие истинно и цикл начинает выполняться. Условие проверяется при каждом нажатии клавиши пользователем. При вводе символа А условие становится ложным и выполнение цикла прекращается.
Как и в цикле for, в цикле while условие проверяется перед началом итерации. Это значит, что если условие ложно, тело цикла не будет выполнено. Благодаря этому нет необходимости вводить в программу отдельное условие перед циклом. Рассмотрим это на примере функции pad(), которая добавляет пробелы в конец строки и делает ее длину равной предварительно заданной величине. Если строка уже имеет необходимую длину, то пробелы не добавляются:
#include <stdio.h>
#include <string.h>

void pad(char *s, int length);

int main(void)
{
  char str[80];

  strcpy(str, "это проверка");
  pad(str, 40);
  printf("%d", strlen(str));

  return 0;
} 

/* Добавление пробелов в конец строки. */
void pad(char *s, int length)
{
  int l;

  l = strlen(s); /* опредление длины строки */

  while(l<length) {
    s[l] = ' '; /* вставка пробелов */
    l++;
  }
  s[l]= '\0'; /* строка должна заканиваться нулем */
}
Аргументами функции pad() являются s (указатель на исходную строку) и length (требуемое количество символов в строке). Если длина строки s при входе в функцию равна или больше length, то цикл while не выполняется. В противном случае pad() добавляет требуемое количество пробелов, а библиотечная функция strlen() возвращает длину строки.
Если выполнение цикла должно зависеть от нескольких условий, можно создать так называемую управляющую переменную, значения которой присваиваются разными операторами тела цикла. Рассмотрим следующий пример:
void func1(void)
{
  int working;

  working = 1; /* т.е. ИСТИНА */

  while(working) {
    working = process1();
    if(working)
      working = process2();
    if(working)
      working = process3();
  }
}
В этом примере переменная working является управляющей. Любая из трех функций может возвратить значение 0 и этим прервать выполнение цикла. Тело цикла while может быть пустым. Например, цикл
while((ch=getchar()) != 'A') ;
выполняется до тех пор, пока пользователь не введет символ 'А'. Напоминаем, что оператор присваивания выполняет две задачи: присваивает значение выражения справа переменной слева и возвращает это значение как свое собственное.

Цикл do-while

В отличие от циклов for и while, которые проверяют свое условие перед итерацией, do-while делает это после нее. Поэтому цикл do-while всегда выполняется как минимум один раз. Общая форма цикла do-while следующая:
do {
оператор;
} while (условие);
Если оператор не является блоком, фигурные скобки не обязательны, но их почти всегда ставят, чтобы оператор достаточно наглядно отделялся от условия. Итерации оператора do-while выполняются, пока условие не примет значение ЛОЖЬ.
В следующем примере в цикле do-while числа считываются с клавиатуры, пока не встретится число, меньшее или равное 100:
do {
  scanf("%d", &num);
} while(num > 100);
Цикл do-while часто используется в функциях выбора пунктов меню. Если пользователь вводит допустимое значение, оно возвращается в качестве значения функции. В противном случае цикл требует повторить ввод. Следующий пример демонстрирует усовершенствованную версию программы для выбора пункта меню проверки грамматики:
void menu(void)
{
  char ch;

  printf("1. Проверка правописания\n");
  printf("2. Коррекция ошибок\n");
  printf("3. Вывод ошибок\n");
  printf("      Введите Ваш выбор: ");

  do {
    ch = getchar(); /* чтение выбора с клавиатуры */
    switch(ch) {
      case '1':
        check_spelling();
        break;
      case '2':
        correct_errors();
        break;
      case '3':
        display_errors();
        break;
    }
  } while(ch!='1' && ch!='2' && ch!='3');
}
В этом примере применение цикла do-while весьма уместно, потому что итерация, как уже упоминалось, всегда должна выполниться как минимум один раз. Цикл повторяется, пока его условие не станет ложным, т.е. пока пользователь не введет один из допустимых ответов.


[1]Этот пример, конечно, учебный. На практике так поступать со строкой не рекомендуется, потому что начало строки str, "напрасно висящее" в памяти, впоследствии может создать некоторые трудности. Например, если вы захотите освободить память, занимаемую данной строкой, вам потребуется указать на начало строки, а не на первый отличный от пробела символ в этой строке.
[2]В некоторых языках (например АЛГОЛ 68) локализация параметра цикла выполняется автоматически.

Комментарии