
Int main(int argc, char* argv[])
{
int key;
loop:
printf("\nEnter command (F, M or Q): ");
key = getche(); // Прочитать клавишу.
switch (key)
{ case ‘F’: printf("\n File selected.\n");
break;
case 'm':
case 'M':
printf ("\n Message selected.\n");
break;
case 'q':
case 'Q':
printf("\n Quit selected.\n");
printf("\nPress a key to Exit...");
getch() ;
return 0; // Возврат
default: printf("\n invalid command! \n") ;
}
goto loop; // Следующая команда.
}
Мы организовали в программе простейший “бесконечный” цикл, который все время просит пользователя ввести команду — до тех пор, пока не будет нажата клавиша “Q”. В этом случае происходит возврат в операционную систему.
Чтение
команды производится функцией getche().
Она, как и getch (), возвращает код нажатой
клавиши, однако в отличие от getch
() отображает введенный символ на экране.
Для реализации цикла мы использовали архаический оператор goto, исполнение которого приводит к передаче управления на метку, указанную в операторе. В примере метка с именем loop стоит в самом начале программы. Таким образом, дойдя до конца функции main (), управление возвращается к ее началу и все повторяется снова.
Как
я сказал, оператор goto весьма архаичен,
и он, конечно, никак не укладывается в
концепцию структурированного потока
управления. Единственным случаем, когда
его использование в программе на С может
быть оправдано, является обработка
некоторых аварийных ситуаций. Если
имеется очень сложная система вложенных
друг в друга управляющих структур и
ошибка происходит где-то глубоко внутри,
то проще и надежнее всего передать
управление на процедуру обработки
ошибок с помощью goto.
Циклы
В языке С структуры многократного повторения реализуются тремя разновидностями операторов цикла. Это циклы while, do... while и for.
Цикл while
while (условие продолжения) оператор
Сначала оценивается условие_продолжения. Если оно истинно, выполняется оператор, после чего управление возвращается заголовку цикла, и все повторяется снова. Когда условие оказывается ложным, управление передается следующему после цикла оператору.
Как обычно, одиночный оператор тела цикла можно заменить блоком, заключенным в фигурные скобки:
whi1е (условие_продолжения)
{
операторы_тела цикла
}
Используя этот оператор, можно было бы переписать предыдущий пример (со структурой switch) следующим образом:
Int main(int argc, char* argv[ ])
{
int key, done = 0;
while (!done)
{
printf("\nEnter command (F, M or Q): ");
key = getche(); // Прочитать клавишу.
switch (key)
{
case 'f':
case 'F': printf("\nFile selected.\n");
break;
case 'm':
case 'M': printf("\n Message selected.\n");
break;
case 'q':
case 'Q': printf("\n Quit selected.\n");
done = 1; // Завершить цикл.
break;
default: printf("\nlnvalid command!\n") ;
}
}
printf("\nPress a key to Exit...");
getch() ;
return 0; // Возврат в Windows.
}
Это более “грамотная” версия цикла обработки команд. Пока done равняется нулю, цикл продолжает выполняться. Когда нажимают клавишу 'q', done присваивается единица и при очередной оценке условия оно оказывается ложным - цикл завершается.
Обратите внимание, что в цикле while проверка условия делается перед выполнением тела цикла. Если условие изначально ложно, то тело цикла не исполняется вообще, ни одного раза.
Цикл do—while
do оператор while (условие продолжения);
Здесь сначала выполняется оператор, а затем производится проверка условия_продолжения. Если условие истинно, управление возвращается в начало цикла; если ложно, цикл завершается и управление переходит к оператору, следующему за циклом.
Отличие от предыдущей конструкции очевидно — тело цикла do... while исполняется хотя бы один раз вне зависимости от каких-либо условий, в то время как в цикле while при ложном условии тело не исполняется вообще. Хотя конкретную программную задачу можно решить, применив любую из этих конструкций, чаще всего одно из двух решений оказывается более экономным.
Цикл for
Цикл for, наиболее универсальный из всех циклов языка С, выглядит так:
for ([инициализация]; [условие]; [модификация]) оператор
Прежде всего выполняется инициализация цикла; секция инициализации может содержать любое выражение. Инициализация производится только один раз перед началом работы цикла.
Оценивается выражение условия. Если оно истинно, выполняется оператор тела цикла; если условие ложно, происходит выход из цикла и управление передается следующему оператору.
После исполнения тела цикла производится модификация, после чего управление возвращается заголовку цикла и все повторяется снова. Секция модификации может содержать любое выражение; обычно в ней изменяют значения управляющих переменных цикла.
Как видно из синтаксического описания, любую секцию заголовка цикла for можно опустить, но разделители — точки с запятой — все равно должны присутствовать. Если опущено условие, цикл будет выполняться бесконечно.
Простейшей и самой популярной конструкцией на основе цикла for является цикл с управляющей переменнои-счетчиком:
int i;
for (i =0; i < REPEAT; i++)
DoSomething (i);
Счетчик инициализируется значением 0. В начале каждого прохода цикла проверяется, не достиг ли он значения REPEAT. Как только i станет равным REPEAT, тело цикла пропускается и управление передается следующему оператору. В конце каждого прохода i увеличивается на единицу. Как нетрудно подсчитать, тело цикла выполняется для значений i от О до REPEAT-1, т. е. REPEAT раз.
Любую
конкретную структуру повторения,
требуемую для решения некоторой задачи,
можно реализовать на основе любого из
циклов С, однако всегда какой-то из них
подходит к данному случаю наилучшим
образом, позволяя написать более ясный
и компактный код. Так, если необходимое
число итераций цикла известно заранее
(как при обработке массива), проще всего
применить цикл for. Если же число итераций
заранее определить нельзя, как в нашем
примере обработки команд (момент
завершения цикла определяется
пользователем) или при операциях поиска
в списке, применяют цикл while или do. .
.while.
У структур повторения в ряде ситуаций есть альтернатива. Это рекурсия, заключающаяся в том, что функция вызывает саму себя. Естественно, такой вызов должен быть условным, т. е. обязательно должен наступить такой момент, когда на очередном шаге рекурсивного вызова не происходит. Есть классический пример рекурсии — вычисление факториала:
unsigned Fac(unsigned n)
{
if (n) return n * Fac(n - 1);
else return 1;
}
Когда аргумент в очередном вызове оказывается равен 0, рекурсия завершается — функция возвращает 1. До этого момента не происходило, по существу, реальных вычислений (умножений). На стеке накапливались вызовы Fac () с последовательно уменьшающимися аргументами. Теперь стек начинает “разматываться”, и возвращаемые на каждом шаге значения умножаются на последовательно увеличивающиеся n. Глубина рекурсии ограничивается только размером стека программы.
Факториал, конечно, вообще никто никогда не вычисляет, во всяком случае, подобным образом. Однако существуют задачи, для которых метод рекурсии оказывается естественным решением и реализуется намного проще, чем это можно
было бы сделать с помощью циклов. Таковы, прежде всего, различные операции над древовидными структурами данных.
Не все языки программирования допускают рекурсию. Мы напомнили о ней просто для того, чтобы подчеркнуть, что С — один из тех языков, в которых рекурсия возможна.
Операторы прерывания блока
Часто бывает необходимо “досрочно” выйти из некоторого цикла, до того, как будет удовлетворено условие его завершения (говоря точнее, до того, как условие продолжения станет ложным). Например, вы просматриваете массив на предмет поиска заданного значения. Как только нужный элемент массива найден, выполнять цикл далее нет необходимости. Для досрочного завершения циклов в С применяются операторы break, return и continue. С оператором break вы уже встречались — помимо циклов, он используется в блоках switch.
-
Оператор break вызывает прерывание ближайшего (самого внутреннего) заключающего его блока switch, while, do... while или for. Управление немедленно передается следующему за блоком оператору.
-
Оператор continue воздействует только на блоки циклов. Он передает управление в конец тела цикла, пропуская, таким образом, все следующие за ним операторы блока. Здесь досрочно завершается не сам цикл, а его текущая итерация.
-
Разумеется,
операторы прерывания циклов должны
выполняться условно, т. е. должны входить
в блок if или else некоторого условного
оператора и исполняться только при
наступлении условий досрочного завершения
цикла.
Эти два оператора эквивалентны следующим конструкциям с goto:
// Эквивалент break:
while (...)
{
…
goto brkLabel;
…
}
brkLabel:
// Эквивалент continue:
while (...)
{
…
goto cntLabel;
…
ntLabel:; // Пустой помеченный оператор.
}
Использование
break и continue для прерывания циклов, вообще
говоря, нежелательно — по тем же причинам,
что и использование goto (от операторов
break в структуре выбора switch никуда не
уйти). Они нарушают структурную организацию
потока управления и затрудняют чтение
текста программы.
-
Оператор return прерывает выполнение текущей функции и возвращает ее значение в вызывающую программу. Он имеет вид:
return [выражение];
Если
функция “возвращает” тип void, выражение
опускается. Если код функции при ее
вызове не исполняет ни одного оператора
return, подразумевается, что return присутствует
в качестве последнего оператора тела
функции. Возвращаемое значение при этом
не определено.
Не следует ставить операторы return где попало. Вернитесь и посмотрите на листинг 3.3. Это пример того, как не надо программировать. По правилам “хорошего тона” оператор возврата должен быть только один (от силы два) и он должен располагаться в конце тела функции.
Блоки и локальные переменные
Поскольку при описании управляющих конструкций мы попутно ввели понятие блока, нужно сделать одно уточнение касательно объявлений и области действия локальных переменных. На самом деле локальные переменные могут объявляться не только в начале тела функции, но и в любом другом блоке (if, while и т. д.). Областью действия переменной является блок, в котором она объявлена; она скрывает любую переменную с тем же именем, объявленную вне данного блока. За пределами блока переменная недоступна.
Массивы и указатели
Массивы и указатели довольно тесно связаны между собой. Имя массива можно разыменовывать, как указатель. В свою очередь, указатель можно индексировать, как массив, если это имеет смысл. Поэтому мы рассматриваем массивы и указатели в одном разделе.
Массивы
Массив по существу является совокупностью однотипных переменных (элементов массива), объединенных под одним именем и различающихся своими индексами. Массив объявляется подобно простой переменной, но после имени массива указывается число его элементов в квадратных скобках:
int myArray[8];
Массив, как и переменную, можно инициализировать при объявлении. Значения для последовательных элементов массива отделяются друг от друга запятыми и заключаются в фигурные скобки:
int iArray[8] = {7, 4, 3, 5, 0, 1, 2, 6);
Обращение к отдельным элементам массива производится путем указания индекса элемента в квадратных скобках, например:
myArray[3] = 11;
myArray[i] = iArray[7-i];
Индекс должен быть целым выражением, значение которого не выходит за пределы допустимого диапазона. Поскольку индексация массивов начинается в С всегда с нуля (т. е. первый элемент имеет индекс 0), то, если массив состоит из N элементов, индекс может принимать значения от О до N-1.
В
языке С не предусмотрена автоматическая
проверка допустимости значений индекса
времени выполнения, поэтому при индексации
массивов нужно быть внимательным. Выход
индекса за границы массива может
приводить к совершенно непредсказуемым
результатам.
Массивы естественным образом сочетаются с циклами for. Мы приведем пример программы, работающей с массивом целых чисел. Она выполняет так называемую “пузырьковую сортировку” введенных пользователем чисел в порядке возрастания.
Работу программы иллюстрирует рис. 3.4.
Листинг 3.4. Программа пузырьковой сортировки
/*
** Loop.с: Программа пузырьковой сортировки.
*/
#pragma hdrstop
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
// Функция сортировки
void DoSort(int array[], int n)
{
int i, j, temp;
for (i = n-1; i > 0; i--)
for (j = 0; j < i; j++)
if (array[j] > array[j+l])
{
temp = array[j];
array[j] = array[j+l];
array [j+1] = temp;
}
}
#pragma argsused