Отправляет email-рассылки с помощью сервиса Sendsay

C и C++ для начинающих

  Все выпуски  

C и C++ для начинающих


Служба Рассылок Subscribe.Ru проекта Citycat.Ru
Доброго времени суток! Сегодня мы продолжим речь об управляющих конструкциях.

Как вы могли догадаться, одними условиями С не ограничивается. Слегка изменим условие нашей первой программы: напечатать слова "Hello world" 100 раз. Печатать 100 выражений? Не выход. А если количество повторов заранее не известно? На помощь приходит управляющая конструкция цикла.

Что есть цикл? Цикл есть некоторый участок кода, который будет выполняться несколько раз. Более того, количество повторений в общем случае неизвестно, и уж тем более неизвестно на этапе компиляции. Начнем мы с самого простого и единственного действительно необходимого цикла - цикла с предусловием. Этот цикл чем-то похож на ветвление, кроме одной детали: при ветвлении блок выполнится, если условие истинно, а в цикле блок выполняется, пока условие остается истинным. Оформление тоже сходно:
 while (условие)
  выражение;
Все требования у условию и к выражению такие же, как и в конструкции ветвления. Теперь реализуем поставленную задачу:
 int main (void)
 {
  int c;

  c = 100;
  while (c--)
   printf ("Hello world\n");
  return 0;
 }
Что-нибудь понятно? :-) Сейчас объясню поподробнее. Как вы помните, понятие истины в С растяжимо: истинно все, что не равно нулю. В частности, 100 - это истина. А выражение c--, стоящее в условии (помните постфиксную форму инкремента?), вернет значение переменной с, а затем уменьшит его на единицу. При выполнении цикла в сотый раз условие вернет 1, а в переменной с окажется 0. После этого условие выполнится в 101-й раз, вернет 0, запишет в с -1, и, так как 0 - это ложь, цикл выполняться больше не будет.

Обратите внимание: если цикл выполнился в общем случае n раз, то условие было проверено n+1 раз. Это всегда. Поэтому в нашем случае, когда мы моделировали цикл со счетчиком, значение счетчика получило следующее значение. Запомните этот эффект: он нам пригодится в дальнейшем.

В принципе, этого при должной сноровке должно хватить. Но разработчики позаботились о том, чтобы нам было удобно. Скажем, у нас есть достаточно большой цикл и в середине цикла выполнилось некоторое действие, в результате чего дальнейшее выполнение цикла стало бессмысленным. Как реализовать выход из цикла в этом случае? Можно, например, вот так:
 int f;

 f = 1;
 while (условие && f)
 {
  выражения;
  ...
  другие_выражения;

  if (наше_условие)
   f = 0;
  if (f)
  {
   ...
   остальное_тело_цикла;
   ...
  }
 }
Ну, хорошо. А если условий 10? Вводить десять синальных переменных (переменные такого назначения называют флаговыим переменными или просто флагами) и конструировать сложную конструкцию условия вряд ли кому захочется. К счастью, у нас в распоряжении есть зарезервированное слово (далее я буду называть их также ключевыми словами) break. Оно осуществляет немедленный безусловный выход из цикла, то есть переход на первый оператор, непосредственно следующий за циклом. Теперь пример можно переписать так:
 while (условие)
 {
  выражения;
  ...
  другие_выражения;

  if (наше_условие)
   break;

  ...
  остальное_тело_цикла;
  ...
 }

Какой из выриантов показался вам удобнее? Я думаю, что второй. Но не злоупотребляйте этой возможностью! Если у вас break'ов больше, чем условий, подумайте, как можно изменить цикл. И уж во всяком случае избегайте таких конструкций:
 while (1)
 {
  тело_цикла;
  if (условие)
   break;
 }
Это пример очень плохого стиля программирования. Если такой цикл со множественными выходами растянется хотя бы на пять страниц, то вы уже перестанете в нем разбираться. И ради бога, никогда не заявляйте, что не станене писать циклов на пять страниц и что уж в своей программе-то вы разберетесь всегда! НЕ РАЗБЕРЕТЕСЬ. Те, кто раньше писал на каком-нибудь языке, пусть проведет такой эксперимент: возьмет любую программу, к которой не прикасался полгода, и попытается воссоздать ее работу. Как впечатления? Тем, кто такой возможности не имеет, я советую особо не расстраиваться - вы еще столкнетесь с такой проблеммой, хотя я желаю вам никогда с ней не сталкиваться.

Ладно, вернемся к теме. В С есть еще одно ключевое слово, влияющее на ход цикла - это слово continue. Оно вызывает завершение не всего цикла, а только его текущей итерации.

А теперь вопрос: как узнать, выполнился ли цикл полностью или был прерван словом break? Если вы внимательно читали, то ответите сразу. Если цикл выполнился полностью (скажем n раз), то условие было проверено n+1 раз. Значит, в результате выполнения цикла
 int i;

 i = 0;
 while (i++ < 5)
  printf ("%d\n", i);
значение переменной i станет равной 5. Если цикл был прерван, то оно будет лежать в интервале от 0 до 4.

Кроме этого цикла есть еще цикл с постусловием. Оформляется он так:
 do
  выражение;
 while (условие);
Его особенность в том, что проверка условия происходит не перед выполнением тела цикла, а после него, поэтому тело цикла обяэательно выполнится хотя бы один раз. Иногда это бывает полезно. В остальном этот цикл сходен с while.

И теперь особенность языка С - цикл for. Для начала дам его оформление:
 for (выражение_1; условие; выражение_2)
  выражение_3;
Что-то новенькое, правда? По крайней мере, больше ни в одном из известный мне языков такого нет. Что это представляет из себя на самом деле? Вот что:
 выражение_1;
 while (условие)
 {
  выражение_3;
  выражение_2;
 }
На самом деле его действительно часто применяют как цикл со счетчиком, например, так:
 /* Третий вариант нашего задания */
 int i;

 for (i = 0; i < 100; i++)
  printf ("Hello world\n");
Здесь выражение_1 используется как инициализация счетчика цикла, условие - для проверки на конец цикла, и выражение_2 - для изменения счетчика. Любое выражение может быть пропущено, то есть
 for (; i < 5;)
эквивалентно
 while (i < 5)
Однако такое использование цикла for не рекомендуется. Лучше использовать каждый цикл для того, для чего он предназначен.

Да, хоть результат выполнения выражения_1 и выражения_2 игнорируется, все же к ним предъявляются те же требования, что и к условию.

Теперь полезно рассказать еще об одном операторе - запятой. Этот опреатор имеет наинизший приоритет и ничего, по существу, не делает. Однако, так как это оператор, вычисляются оба операнда, причем компилятор гарантирует их вычисление слева направо. Оператор возвращает значение правого операнда. Пример:
 int i;

 i = 1, 2, 3, 4, 5;
Чему равно значение i? Подумайте. Правильный ответ - единице, так как оператор запятая имеет более низкий приоритет, чем оператор присваивания. Тем не менее, этот оператор вернет значение 5, чему можно убедиться, написав так:
 int i;

 printf ("%d\n", (i = 1, 2, 3, 4, 5));
Из этого примера можно выяснить еще одно правило: если вы хотите заставить запятую работать описанным образом там, где ее функция отличается от описанной, выражение надо брать в скобки. Например, здесь запятая разделяет параметры функции printf.

Для чего это все нужно? На самом деле запятая находит множество различных применений. Вот одно из них. После всего рассказанного вы легко поймете, как работает такой участок кода:
 int i, j;

 for (i = 0, j = 10; i * j > 30; i++, j--)
  printf ("%d * %d = %d", i, j, i * j);
Надеюсь, вам понравилось. В С можно создавать и более интересные вещи.

Любая управляющая конструкция может быть вложена в другую. При этом слова break и continue распространяются только на ту конструкцию, в которой они находятся непосредственно.

Резюмируя, напишем простенькую программу нахождения всех простых чисел от 1 до 100:
 #include <stdio.h>

 int main (void)
 {
  int i, d, l;

  l = 100;
  for (i = 2; i < l; i++)
  {
   for (d = 2; i % d; i++)
    ;
   if (d == i)
    printf ("%d\n", i);
  }
  return 0;
 }
Я надеюсь, что вы легко разберетесь в этой программе.

И, наконец, еще об одной управляющей конструкции - столь любимой всеми конструкции безусловного перехода goto. Синтаксис ее таков:
 метка:
 ...
 goto метка;
А теперь, когда вы поняли, как ее использовать, я вас прошу, можно сказать, даже требую:
ИЗБЕГАЙТЕ ИСПОЛЬЗОВАТЬ goto!!!
Более простого способа сделать программу нечитабельной и непригодной для дальнейшего совершенствования и развития не существует с момента появления вычислительной техники. Более того, прыжки внутрь условий, циклов и из них могут привести к непредсказуемой работе программы. Запомните: на С любой алгоритм может быть реализован полностью структурно. Те, кто никогда не писал на Бейсике и на Фортране и не привык использовать goto: счастливые люди... И не привыкайте. Просто забудьте о его существовании. Тем, кто уже имеет некоторый опыт в написании программ на указанных языках, придется отучиваться. Может быть, моя позиция покажется некоторым слишком жесткой, но все же: если у человека в программе есть хоть одно слово goto, то писать программы он не умеет. Да, и не говорите мне, кто знает, о множестве вложенных конструкций, в которых где-то внутри возникает ошибка, требующая выхода на самый верхний уровень для обработки. Просто подождите несколько выпусков. Как только мы начнем С++, первое, о чем я расскажу, в нарушение всех правил - это о структурной обработке ошибок.

На этом тему управляющих конструкций мы закроем. Домашнее задание:
  1. Напишите программу, которая будет вычислять сумму бесконечного ряда
    1 + 1/(1!) + 1/(2!) + 1/(3!) + ... ,
    до тех пор, пока последний член не станет меньше, чем 10-4 (используйте тип double).
  2. Что будет напечатано в результате выполнения следующей программы?
     #include <stdio.h>
    
     int main (void)
     {
      int i;
    
      for (i = 36; i; i /= 2)
       printf ("%d\n", i);
      return 0;
     }
      
  3. Напишите программу, печатающую такую фигуру:
    $$$$$$$
    $$$$$$
    $$$$
    $$$
    $$
    $
      
    - с помощью вложенного цикла.

За сим все. До встречи!

Ведущий рассылки, av

http://subscribe.ru/
E-mail: ask@subscribe.ru

В избранное