| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780 |
- \chapter{Управление}
- \label{chapt:control_flow}
- Порядок, в котором выполняются вычисления, определяется инструкциями управления.
- Мы уже встречались с наиболее распространёнными управляющими конструкциями
- такого рода в предыдущих примерах; здесь мы завершим их список и более точно
- определим рассмотренные ранее.
- \section{Инструкции и блоки}
- \index{выражение-инструкция}%
- \index{точка с запятой}%
- Выражение, скажем \verb|x = 0|, или \verb|i++|, или \verb|printf(...)|,
- становится инструкцией, если в конце его поставить точку с запятой, как,
- например, в записи
- \begin{ShortCodePar}
- x = 0;
- i++;
- printf(...);
- \end{ShortCodePar}
- \noindent В Си точка с запятой является заключающей литерой инструкции, а не
- разделителем, как в языке Паскаль.
- \index{блок}%
- \index{инструкция!окончание}%
- \index{составная инструкция}%
- \index{инструкция!составная}%
- \index{фигурные скобки}%
- Фигурные скобки \verb|{| и \verb|}| используются для объединения описаний и
- инструкций в \emph{составную инструкцию}, или \emph{блок}, чтобы с точки зрения
- синтаксиса эта новая конструкция воспринималась как одна инструкция. Фигурные
- скобки, обрамляющие группу инструкций, образующих тело функции, -- это один
- пример; второй пример -- это скобки, объединяющие инструкции, помещённые после
- \verb|if|, \verb|else|, \verb|while| или \verb|for|.
- \index{блок!структура}%
- (Переменные могут быть описаны внутри \emph{любого} блока, об этом разговор
- пойдёт в гл.~\ref{chapt:functions_and_program_structure}) После правой
- закрывающей фигурной скобки в конце блока точка с запятой не ставится.
- \section{Конструкция \texorpdfstring{\protect\Verb|if-else|}{if-else}}
- \index{if-else@\texttt{if-else}!инструкция}%
- Инструкция \verb|if-else| используется для принятия решения. Вот её синтаксис:
- \begin{ShortCodeParWithCC}{\\\{\}}
- if (\textit{выражение})
- \textit{инструкция}\textsubscript{1}
- else
- \textit{инструкция}\textsubscript{2}
- \end{ShortCodeParWithCC}
- \noindent причём \verb|else|-часть может быть, а может и отсутствовать. Сначала
- вычисляется \textit{выражение}, и, если оно истинно (т.е. отлично от нуля),
- выполняется \textit{инструкция}$_{1}$. Если \textit{выражение} ложно (т.е. его
- значение равно нулю) и существует \verb|else|-часть, то выполняется
- \textit{инструкция}$_{2}$.
- \index{нуль, опущенная проверка на него}%
- Так как \verb|if| просто проверяет числовое значение выражения, условие иногда
- можно записывать в сокращённом виде. Так, запись
- \begin{ShortCodeParWithCC}{\\\{\}}
- if (\textit{выражение})
- \end{ShortCodeParWithCC}
- \noindent короче, чем
- \begin{ShortCodeParWithCC}{\\\{\}}
- if (\textit{выражение} != 0)
- \end{ShortCodeParWithCC}
- \noindent Иногда такие сокращения естественны и ясны, в других случаях,
- наоборот, затрудняют понимание программы.
- \index{неоднозначность \texttt{if-else}}%
- \index{if-else@\texttt{if-else}!неоднозначность}%
- Отсутствие в одной из вложенных друг в друга \verb|if|-конструкций
- \verb|else|-части может привести к неоднозначному толкованию записи. Эту
- неоднозначность разрешают тем, что \verb|else| связывают с ближайшим \verb|if|,
- у которого нет своего \verb|else|. Например, в
- \begin{ShortCodePar}
- if (n > 0)
- if (a > b)
- z = a;
- else
- z = b;
- \end{ShortCodePar}
- \noindent \verb|else| относится к внутреннему \verb|if|, что мы и показали с
- помощью отступов. Если нам требуется иная интерпретация, необходимо должным
- образом расставить фигурные скобки:
- \begin{ShortCodePar}
- if (n > 0) {
- if (a > b)
- z = a;
- }
- else
- z = b;
- \end{ShortCodePar}
- Ниже приводится пример ситуации, когда неоднозначность особенно опасна:
- \begin{LongCodePar}
- if (n >= 0)
- for (i = 0; i < n; i++)
- if (s[i] > 0) {
- printf("...");
- return i;
- }
- else /* НЕВЕРНО */
- printf("ошибка -- отрицательное n\n");
- \end{LongCodePar}
- \noindent%
- \index{отступы в тексте программы}%
- С помощью отступов мы недвусмысленно показали, что нам нужно, однако компилятор
- не воспримет эту информацию и отнесёт \verb|else| к внутреннему \verb|if|.
- Такого рода ошибки особенно тяжело искать. Здесь уместен следующий совет:
- вложенные \verb|if| обрамляйте фигурными скобками.
- \index{точка с запятой}%
- Кстати, обратите внимание на точку с запятой после \verb|z = a| в
- \begin{ShortCodePar}
- if (a > b)
- z = a;
- else
- z = b;
- \end{ShortCodePar}
- \noindent Здесь она обязательна, поскольку по правилам грамматики за \verb|if|
- должна следовать инструкция, а
- \index{выражение-инструкция}%
- выражение-инструкция типа \verb|z = a;| всегда заканчивается точкой с запятой.
- \section{Конструкция \texorpdfstring{\protect\Verb|else-if|}{else-if}}
- \label{sec:else_if}
- \index{многопутевое ветвление}%
- \index{else-if@\texttt{else-if}}%
- Конструкция
- \begin{ShortCodeParWithCC}{\\\{\}}
- if (\textit{выражение})
- \textit{инструкция}
- else if (\textit{выражение})
- \textit{инструкция}
- else if (\textit{выражение})
- \textit{инструкция}
- else if (\textit{выражение})
- \textit{инструкция}
- else
- \textit{инструкция}
- \end{ShortCodeParWithCC}
- \noindent встречается так часто, что о ней стоит поговорить особо. Приведённая
- последовательность инструкций \verb|if| -- самый общий способ описания
- многоступенчатого принятия решения. \textit{Выражения} вычисляются по порядку;
- как только встречается \textit{выражение} со значением <<истина>>, выполняется
- соответствующая ему \textit{инструкция}; на этом последовательность проверок
- завершается. Здесь под словом \textit{инструкция} имеется в виду либо одна
- инструкция, либо группа инструкций в фигурных скобках.
- Последняя \verb|else|-часть срабатывает, если все предыдущие условия не
- выполняются. Иногда в последней части не требуется производить никаких действий,
- в этом случае фрагмент
- \begin{ShortCodeParWithCC}{\\\{\}}
- else
- \textit{инструкция}
- \end{ShortCodeParWithCC}
- \noindent можно опустить или использовать для фиксации ошибочной
- (<<невозможной>>) ситуации.
- В качестве иллюстрации трёхпутевого ветвления рассмотрим функцию бинарного
- поиска значения \verb|x| в массиве \verb|v|. Предполагается, что элементы
- \verb|v| упорядочены по возрастанию. Функция выдаёт положение (число в пределах
- от $0$ до $n-1$) \verb|x| в \verb|v|, если оно там встречается, и $-1$, если его
- нет.
- При бинарном поиске значение \verb|x| сначала сравнивается с элементом,
- занимающим серединное положение в массиве \verb|v|. Если \verb|x| меньше, чем
- это значение, то областью поиска становится <<верхняя>> половина массива
- \verb|v|, в противном случае -- <<нижняя>>. В любом случае следующий шаг --
- это сравнение с серединным элементом отобранной половины. Процесс
- <<уполовинивания>> диапазона продолжается до тех пор, пока либо будет найдено
- значение, либо станет пустым диапазон.
- \index{функция!binsearch@\texttt{binsearch}}%
- Запишем функцию бинарного поиска:
- \begin{LongCodePar}
- /* binsearch: найти x в v[0] <= v[1] <= ... <= v[n-1] */
- int binsearch(int x, int v[], int n)
- {
- int low, high, mid;
- low = 0;
- high = n - 1;
- while (low <= high) {
- mid = (low+high) / 2;
- if (x < v[mid])
- high = mid - 1;
- else if (x > v[mid])
- low = mid + 1;
- else /* x найден */
- return mid;
- }
- return -1; /* x не найден */
- }
- \end{LongCodePar}
- \noindent Основное действие, выполняемое на каждом шаге поиска, -- сравнение
- значения \verb|x| (меньше, больше или равно) с элементом \verb|v[mid];| это
- сравнение естественно поручить конструкции \verb|else-if|.
- \paragraph{Упражнение 3.1.} В нашей программе бинарного поиска внутри цикла
- осуществляются две проверки, хотя могла быть только одна (при увеличении числа
- проверок вне цикла). Напишите программу, предусмотрев в ней одну проверку внутри
- цикла. Оцените разницу во времени счёта.
- \section{Переключатель}
- \label{sec:switch}
- \index{switch-инструкция@\texttt{switch}-инструкция}%
- Инструкция \verb|switch| используется для выбора одного из многих путей. Она
- проверяет, совпадает ли значение выражения с одним из значений, входящих в
- некоторое множество целых \emph{констант}, и выполняет соответствующую этому
- значению ветвь программы:
- \begin{ShortCodeParWithCC}{\\\[\]}
- switch (\textit[выражение]) {
- case \textit[конст-выр]: \textit[инструкции]
- case \textit[конст-выр]: \textit[инструкции]
- default: \textit[инструкции]
- }
- \end{ShortCodeParWithCC}
- \noindent%
- \index{метка!case@\texttt{case}}%
- \index{case-метка@\texttt{case}-метка}%
- Каждая ветвь \verb|case|
- помечена одной или несколькими целочисленными константами или же
- \index{выражение!константное}%
- \index{константное выражение}%
- константными выражениями. Вычисления начинаются с той ветви \verb|case|, в
- которой константа совпадает со значением выражения. Константы всех ветвей
- \verb|case| должны отличаться друг от друга.
- \index{метка!default@\texttt{default}}%
- \index{default@\texttt{default}}%
- Если выяснилось, что ни одна из констант не подходит, то выполняется ветвь,
- помеченная словом \verb|default|, если таковая имеется, в противном случае
- ничего не делается. Ветви \verb|case| и \verb|default| можно располагать в любом
- порядке.
- \index{программа!подсчёта!литер}%
- \index{программа!подсчёта!пробельных литер}%
- В гл.~\ref{chapt:tutorial_intro} мы написали программу, подсчитывающую число
- вхождений в текст каждой цифры, пробельных литер (пробелов, табуляций и
- новых-строк) и всех остальных литер. В ней мы использовали последовательность
- \verb|if...else if...else|. Теперь приведём вариант этой программы с
- переключателем:
- \begin{LongCodePar}
- #include <stdio.h>
- main() /* подсчёт цифр, пробелов и прочих литер */
- {
- int c, i, nwhite, nother, ndigit[10];
- nwhite = nother = 0;
- for (i = 0; i < 10; i++)
- ndigit[i] = 0;
- while ((c = getchar()) != EOF) {
- switch (c) {
- case '0': case '1': case '2': case '3': case '4':
- case '5': case '6': case '7': case '8': case '9':
- ndigit[c-'0']++;
- break;
- case ' ': case '\n': case '\t':
- nwhite++;
- break;
- default:
- nother++;
- break;
- }
- }
- printf("цифры =");
- for (i = 0; i < 10; i++)
- printf(" %d", ndigit[i]);
- printf(", пробелы = %d, прочие = %d\n",
- nwhite, nother);
- return 0;
- }
- \end{LongCodePar}
- \index{break@\texttt{break}}%
- Инструкция \verb|break| вызывает немедленный выход из переключателя. Поскольку
- выбор ветви \verb|case| реализуется как переход на метку, то после выполнения
- одной ветви \verb|case|, если ничего не предпринять, программа \emph{провалится
- вниз} на следующую ветвь. Инструкции \verb|break| и \verb|return| -- наиболее
- распространённые средства выхода из переключателя. Инструкция \verb|break|
- используется также для принудительного выхода из циклов \verb|while|, \verb|for|
- и \verb|do-while| (мы ещё поговорим об этом чуть позже).
- <<Сквозное>> выполнение ветвей \verb|case| вызывает смешанные чувства. С одной
- стороны, это хорошо, поскольку позволяет несколько ветвей \verb|case| объединить
- в одну, как мы и поступили с цифрами в нашем примере. Но с другой -- это
- означает, что в конце почти каждой ветви придётся ставить \verb|break|, чтобы
- избежать перехода к следующей. Последовательный проход по ветвям -- вещь
- ненадёжная, он чреват ошибками, особенно при изменении программы. За исключением
- случая с несколькими метками для одного вычисления, старайтесь по возможности
- редко пользоваться сквозным проходом, но если уж вы его применяете, обязательно
- комментируйте эти особые места.
- Добрый вам совет: даже в конце последней ветви (после \verb|default| в нашем
- примере) помещайте инструкцию \verb|break|, хотя с точки зрения логики в ней нет
- необходимости. Но эта маленькая предосторожность спасёт вас, когда однажды вам
- потребуется добавить в конец ещё одну ветвь \verb|case|.
- \paragraph{Упражнение 3.2.} Напишите функцию \verb|escape(s,t)|, которая при
- копировании текста из \verb|t| в \verb|s| преобразует литеры типа новая-строка и
- табуляция в <<видимые последовательности литер>> (типа \verb|\n| и \verb|\t|).
- Используйте инструкцию \verb|switch|. Напишите функцию, выполняющую обратное
- преобразование эскейп-последовательностей в настоящие литеры.
- \section{Циклы
- \texorpdfstring{\protect\Verb|while|}{while} и
- \texorpdfstring{\protect\Verb|for|}{for}}
- \index{while@\texttt{while}!инструкция}%
- Мы уже встречались с циклами \verb|while| и \verb|for|. В цикле
- \begin{ShortCodeParWithCC}{\\\{\}}
- while (\textit{выражение})
- \textit{инструкция}
- \end{ShortCodeParWithCC}
- \noindent вычисляется \textit{выражение}. Если его значение отлично от нуля, то
- выполняется \textit{инструкция}, и вычисление выражения повторяется. Этот цикл
- продолжается до тех пор, пока выражение не станет равным нулю, после чего
- вычисления возобновятся с точки, расположенной сразу за \textit{инструкцией}.
- \begin{samepage}
- \index{for@\texttt{for}!вместо \texttt{while}}%
- \index{for@\texttt{for}!инструкция}%
- \index{while@\texttt{while}!вместо \texttt{for}}%
- Инструкция \verb|for|
- \begin{ShortCodeParWithCC}{\\\{\}}
- for (\textit{выр}\textsubscript{1}; \textit{выр}\textsubscript{2};\
- \textit{выр}\textsubscript{3})
- \textit{инструкция}
- \end{ShortCodeParWithCC}
- \noindent эквивалентна конструкции
- \begin{ShortCodeParWithCC}{\\\[\]}
- \textit[выр]\textsubscript[1];
- while (\textit[выр]\textsubscript[2]) {
- \textit[инструкция]
- \textit[выр]\textsubscript[3];
- }
- \end{ShortCodeParWithCC}
- \noindent если не считать отличий в поведении инструкции \verb|continue|, речь о
- которой пойдёт в разд.~\ref{sec:inst_break_and_continue}.
- \end{samepage}
- С точки зрения грамматики три компоненты цикла \verb|for| представляют собой
- произвольные выражения, но чаще \textit{выр}$_1$ и \textit{выр}$_3$ -- это
- присваивания или вызовы функций, а \textit{выр}$_2$ -- выражение отношения.
- Любое из этих трёх выражений может отсутствовать, но точку с запятой опускать
- нельзя. При отсутствии \textit{выр}$_1$ или \textit{выр}$_3$ считается, что их
- просто нет в конструкции цикла; при отсутствии \textit{выр}$_2$ полагается, что
- его значение как бы всегда истинно.
- \index{бесконечный цикл \texttt{for(;;)}}%
- \index{for(;;) бесконечный цикл@\texttt{for(;;)} бесконечный цикл}%
- Например,
- \begin{ShortCodePar}
- for (;;) {
- ...
- }
- \end{ShortCodePar}
- \noindent есть <<бесконечный>> цикл, выполнение которого, вероятно, прерывается
- каким-то другим способом, например с помощью инструкций \verb|break|, или
- \verb|return|.
- Какой цикл выбрать: \verb|while| или \verb|for| -- это дело вкуса. Так, в
- \begin{ShortCodePar}
- while ((c=getchar()) == ' ' || c == '\n' || c == '\t')
- ; /* обойти пробельные литеры */
- \end{ShortCodePar}
- \noindent нет ни инициализации, ни пересчёта параметра, поэтому здесь больше
- подходит \verb|while|.
- Там, где есть простая инициализация и пошаговое увеличение значения некоторой
- переменной, больше подходит цикл \verb|for|, так как в этом цикле организующая
- его часть сосредоточена в начале записи. Например, начало цикла, обрабатывающего
- первые \verb|n| элементов массива, имеет следующий вид:
- \begin{ShortCodePar}
- for (i = 0; i < n; i++)
- ...
- \end{ShortCodePar}
- \noindent Это похоже на \verb|DO|-циклы в Фортране и \verb|for|-циклы в Паскале.
- Сходство, однако, не очень точное, так как в Си индекс и его предельное значение
- могут изменяться внутри цикла, и значение индекса \verb|i| после выхода из цикла
- всегда определено.
- Поскольку три компоненты цикла могут быть произвольными выражениями, организация
- \verb|for|-циклов не ограничивается только случаем арифметической прогрессии.
- Однако включать в заголовок цикла вычисления, не имеющие отношения к
- инициализации и инкрементации, считается плохим стилем. Заголовок лучше оставить
- только для операций управления циклом.
- \index{функция!atoi@\texttt{atoi}}%
- В качестве более внушительного примера приведём другую версию программы
- \verb|atoi|, выполняющей преобразование стринга в его числовой эквивалент. Это
- более общая версия по сравнению с рассмотренной в
- гл.~\ref{chapt:types-operators-expressions}, в том смысле, что она игнорирует
- левые пробельные литеры (если они есть) и должным образом реагирует на знаки
- \verb|+| и \verb|-|, которые могут стоять перед цепочкой цифр. (В
- гл.~\ref{chapt:functions_and_program_structure} будет рассмотрен вариант
- \verb|atof|, который осуществляет подобное преобразование для чисел с плавающей
- точкой.)
- Структура программы отражает вид вводимой информации:
- \begin{ShortCodeParWithCC}{\\\{\}}
- \textit{проигнорировать пробельные литеры, если они есть}
- \textit{получить знак, если он есть}
- \textit{взять целую часть и преобразовать её}
- \end{ShortCodeParWithCC}
- \noindent На каждом шаге выполняется определённая часть работы и чётко
- фиксируется её результат, который затем используется на следующем шаге.
- Обработка данных заканчивается на первой же литере, которая не может быть частью
- числа.
- \begin{LongCodePar}
- #include <ctype.h>
- /* atoi: преобразование s в целое число; версия 2 */
- int atoi(char s[])
- {
- int i, n, sign;
- for (i = 0; isspace(s[i]); i++)
- ; /* проигнорировать пробельные литеры */
- sign = (s[i] == '-') ? -1 : 1;
- if (s[i] == '+' || s[i] == '-') /* пропуск знака */
- i++;
- for (n = 0; isdigit(s[i]); i++)
- n = 10 * n + (s[i] - '0');
- return sign * n;
- }
- \end{LongCodePar}
- \noindent Заметим, что в стандартной библиотеке имеется более совершенная
- функция преобразования стринга в длинное целое -- \verb|strtol| (см.
- разд.~\ref{apx:sect:general_puporse_functions_stdlib}
- приложения~\ref{apx:stdlib}).
- Преимущества, которые даёт централизация управления циклом, становятся ещё более
- очевидными, когда несколько циклов вложены друг в друга.
- \index{программа!сортировки}%
- \index{сортировка!численная}%
- \index{функция!shellsort@\texttt{shellsort}}%
- \index{числа!сортировка}%
- Проиллюстрируем их на примере сортировки массива целых чисел методом
- \index{Шелл Д. Л.}%
- Шелла, предложенным им в 1959 г. Основная идея этого алгоритма в том, что на
- ранних стадиях сравниваются далеко отстоящие друг от друга, а не соседние
- элементы, как в обычных перестановочных сортировках. Это приводит к быстрому
- устранению массовой неупорядоченности, благодаря чему на более поздней стадии
- остаётся меньше работы. Интервал между сравниваемыми элементами постепенно
- уменьшается до единицы, и в этот момент сортировка сводится к обычным
- перестановкам соседних элементов. Программа \verb|shellsort| имеет следующий
- вид:
- \begin{LongCodePar}
- /* shellsort: сортируются v[0]...v[n-1] в возр.порядке */
- void shellsort(int v[], int n)
- {
- int gap, i, j, temp;
- for (gap = n/2; gap > 0; gap /= 2)
- for (i = gap; i < n; i++)
- for (j=i-gap; j>=0 && v[j]>v[j+gap]; j-=gap) {
- temp = v[j];
- v[j] = v[j+gap];
- v[j+gap] = temp;
- }
- }
- \end{LongCodePar}
- Здесь использованы три вложенных друг в друга цикла. Внешний управляет
- интервалом \verb|gap| между сравниваемыми элементами, сокращая его с помощью
- деления пополам от \verb|n/2| до нуля. Средний цикл перебирает элементы.
- Внутренний -- сравнивает каждую пару элементов, отстоящих друг от друга на
- расстоянии \verb|gap|, и осуществляет перестановку элементов в неупорядоченных
- парах. Так как \verb|gap| обязательно будет сведён к единице, все элементы в
- конечном счёте будут упорядочены. Обратите внимание на то, что универсальность
- цикла \verb|for| позволяет сделать внешний цикл по форме похожим на другие, хотя
- он и не является циклом типа арифметической прогрессии.
- \index{оператор!запятая \texttt{,}}%
- Последний оператор из числа операторов Си <<\verb|,|>> чаще всего используют в
- инструкции \verb|for|. Пара выражений, разделённых запятой,
- \index{вычисление, порядок}%
- \index{порядок!вычислений}%
- вычисляется слева направо. Типом и значением результата являются тип и значение
- правого выражения, что позволяет в инструкции \verb|for| в каждой из трёх
- компонент иметь по нескольку выражений, например вести два индекса параллельно.
- \index{функция!reverse@\texttt{reverse}}%
- Продемонстрируем это на примере функции \verb|reverse(s)|, которая
- <<переворачивает>> стринг \verb|s|, оставляя результат в том же стринге
- \verb|s|:
- \begin{LongCodePar}
- #include <string.h>
- /* reverse: переворачивает стринг s (результат в s) */
- void reverse(char s[])
- {
- int c, i, j;
- for (i = 0, j = strlen(s)-1; i < j; i++, j--) {
- c = s[i];
- s[i] = s[j];
- s[j] = c;
- }
- }
- \end{LongCodePar}
- \noindent Запятые, разделяющие аргументы функции, переменные в описаниях и т.д.
- \emph{не} являются операторами-запятыми и не обеспечивают вычислений слева
- направо.
- Запятыми как операторами следует пользоваться умеренно. Более всего они уместны
- в конструкциях, которые тесно связаны друг с другом (как в \verb|for|-цикле
- программы \verb|reverse|), а также в макросах, в которых многоступенчатые
- вычисления должны быть выражены одним выражением. Запятой-оператором в программе
- \verb|reverse| можно было бы воспользоваться и при обмене литерами в проверяемых
- парах элементов стринга, мысля этот обмен как одну отдельную операцию:
- \begin{ShortCodePar}
- for (i = 0, j = strlen(s)-1; i < j; i++, j--)
- c = s[i], s[i] = s[j], s[j] = c;
- \end{ShortCodePar}
- \paragraph{Упражнение 3.3.} Напишите функцию \verb|expand(s1,s2)|,
- восстанавливающую сокращённую запись типа \verb|a-z| в стринге \verb|s1| до
- эквивалентной полной записи \verb|abc...xyz| в \verb|s2|. В \verb|s1|
- допускаются буквы (прописные и строчные) и цифры. Следует уметь справляться со
- случаями типа \verb|a-b-c|, \verb|a-z0-9| и \verb|-a-b|. Считайте знак
- \verb|-| в начале или в конце \verb|s1| обычной литерой минус.
- \section{Цикл \texorpdfstring{\protect\Verb|do-while|}{do-while}}
- \label{sec:do_while}
- Как мы говорили в гл.~\ref{chapt:tutorial_intro}, в циклах \verb|while| и
- \verb|for| проверка условия окончания цикла выполняется наверху.
- \index{do-инструкция@\texttt{do}-инструкция}%
- В Си имеется ещё один вид цикла, \verb|do-while|, в котором эта проверка в
- отличие от \verb|while| и \verb|for| делается внизу \emph{после} каждого
- прохождения тела цикла, т.е. после того, как тело выполнится по крайней мере
- один раз.
- Цикл \verb|do-while| имеет следующий синтаксис:
- \begin{ShortCodeParWithCC}{\\\{\}}
- do
- \textit{инструкция}
- while (\textit{выражение});
- \end{ShortCodeParWithCC}
- \noindent%
- \index{вычисление, порядок}%
- Сначала выполняется \textit{инструкция}, затем вычисляется \textit{выражение}.
- Если оно истинно, то \textit{инструкция} выполняется снова и т.д. Когда
- выражение становится ложным, цикл заканчивает работу. Цикл \verb|do-while|
- эквивалентен циклу \verb|repeat-until| в Паскале с той лишь разницей, что в
- первом случае указывается условие продолжения цикла, а во втором -- условие его
- окончания.
- Опыт показывает, что цикл \verb|do-while| гораздо реже используется, чем
- \verb|while| и \verb|for|.
- \index{функция!itoa@\texttt{itoa}}%
- Тем не менее потребность в нём время от времени возникает, как, например, в
- функции \verb|itoa| (обратной по отношению к \verb|atoi|), преобразующей число в
- стринг литер. Выполнить такое преобразование оказалось несколько более сложным
- делом, чем ожидалось, поскольку простые алгоритмы генерируют цифры в обратном
- порядке. Мы остановились на варианте, в котором сначала формируется цепочка
- цифр, а затем она реверсируется.
- \begin{LongCodePar}
- /* itoa: преобразование n в стринг s */
- void itoa(int n, char s[])
- {
- int i, sign;
- if ((sign = n) < 0) /* сохраняем знак */
- n = -n; /* делаем n положительным */
- i = 0;
- do { /* генерируем цифры в обратном порядке */
- s[i++] = n % 10 + '0'; /* следующая цифра */
- } while ((n /= 10) > 0); /* исключить её */
- if (sign < 0)
- s[i++] = '-';
- s[i] = '\0';
- reverse(s);
- }
- \end{LongCodePar}
- \noindent Конструкция \verb|do-while| здесь необходима или по крайней мере
- удобна, поскольку в \verb|s| посылается хотя бы одна литера, даже если \verb|n|
- равно нулю.
- \index{программа!читаемость}%
- В теле цикла одну инструкцию мы выделили фигурными скобками (хотя они и
- избыточны), чтобы неискушённый читатель не принял по ошибке слово \verb|while|
- за \emph{начало} цикла \verb|while|.
- \paragraph{Упражнение 3.4.} При условии, что для представления чисел
- используется дополнительный код, наша версия \verb|itoa| не справляется с самым
- большим по модулю отрицательным числом, значение которого равняется $-2^{n-1}$
- (где $n$ -- размер слова). Объясните, чем это вызвано. Модифицируйте программу
- таким образом, чтобы она давала правильное значение указанного числа независимо
- от машины, на которой выполняется.
- \paragraph{Упражнение 3.5.} Напишите функцию \verb|itob(n,s,b)|, которая
- переводит целое \verb|n| в стринг \verb|s|, представляющий число по основанию
- \verb|b|. В частности, \verb|itob(n,s,16)| помещает в \verb|s| текст числа
- \verb|n| в шестнадцатиричном виде.
- \paragraph{Упражнение 3.6.} Напишите версию \verb|itoa| с дополнительным третьим
- аргументом, задающим минимальную ширину поля. При необходимости преобразованное
- число слева должно дополняться пробелами.
- \section{Инструкции \texorpdfstring{\protect\Verb|break|}{break} и
- \texorpdfstring{\protect\Verb|continue|}{continue}}
- \label{sec:inst_break_and_continue}
- \index{break@\texttt{break}}%
- \index{continue@\texttt{continue}}%
- Иногда бывает удобно выйти из цикла не по результату проверки, осуществляемой в
- начале или в конце цикла, а каким-то другим способом. Такую возможность для
- циклов \verb|for|, \verb|while| и \verb|do-while|, а также для переключателя
- \verb|switch| предоставляет инструкция \verb|break|. Эта инструкция вызывает
- немедленный выход из самого внутреннего из объемлющих её циклов или
- переключателей.
- \index{функция!trim@\texttt{trim}}%
- Следующая функция, \verb|trim|, удаляет из стринга хвостовые литеры пробелов,
- табуляций и новых-строк; \verb|break| используется в ней для выхода из цикла по
- первой обнаруженной справа литере, отличной от названных.
- \begin{LongCodePar}
- /* trim: удаляет хвостовые пробелы, таб. и нов.-строки */
- int trim(char[])
- {
- int n;
- for (n = strlen(s)-1; n >= 0; n--)
- if (s[n] != ' ' && s[n] != '\t' && s[n] != '\n')
- break;
- s[n+1] = '\0';
- return n;
- }
- \end{LongCodePar}
- С помощью функции \verb|strlen| можно получить длину стринга. Цикл \verb|for|
- просматривает его в обратном порядке, начиная с конца, до тех пор, пока не
- встретится литера, отличная от пробела, табуляции и новой-строки. Цикл
- прерывается, как только такая литера обнаружится или \verb|n| станет
- отрицательным (т.е. вся строка будет просмотрена). Убедитесь, что функция ведёт
- себя правильно и в случаях, когда стринг пуст или состоит только из пробельных
- литер.
- Инструкция \verb|continue| в чём-то похожа на \verb|break|, но применяется
- гораздо реже. Она вынуждает ближайший объемлющий её цикл (\verb|for|,
- \verb|while| или \verb|do-while|) начать следующий шаг итерации. Для
- \verb|while| и \verb|do-while| это означает немедленный переход к проверке
- условия, а для \verb|for| -- к приращению шага. Инструкцию \verb|continue|
- можно применять только к циклам, но не к \verb|switch|. Будучи помещённой внутрь
- \verb|switch|, расположенного в цикле, она вызовет переход к следующей итерации
- этого цикла.
- Вот фрагмент программы, обрабатывающий только неотрицательные элементы массива
- \verb|a| (отрицательные -- пропускаются).
- \begin{ShortCodePar}
- for (i = 0; i < n; i++) {
- if (a[i] < 0) /* пропуск отрицательных элементов */
- continue;
- ... /* обработка положительных элементов */
- }
- \end{ShortCodePar}
- \noindent К инструкции \verb|continue| часто прибегают в тех случаях, когда
- оставшаяся часть цикла сложна, а замена условия в нём на противоположное и
- введение еще одного уровня приводит к слишком большому числу уровней
- вложенности.
- \section{Инструкция \texorpdfstring{\protect\Verb|goto|}{goto} и метки}
- \index{метка}%
- \index{goto-инструкция@\texttt{goto}-инструкция}%
- В Си имеются порицаемая многими инструкция \verb|goto| и метки для перехода на
- них. Строго говоря, в этой инструкции нет никакой необходимости, и на практике
- почти всегда можно легко без неё обойтись. До сих пор в нашей книге мы не
- использовали \verb|goto|.
- Однако существуют случаи, в которых \verb|goto| может пригодиться. Наиболее
- типична ситуация, когда необходимо прервать обработку в некоторой глубоко
- вложенной структуре и выйти сразу из двух или б\'{o}льшего числа вложенных
- циклов. Инструкция \verb|break| здесь не поможет, так как она осуществляет выход
- только из самого внутреннего цикла. В качестве примера рассмотрим следующую
- конструкцию:
- \begin{LongCodeParWithCC}{\\\[\]}
- for (...)
- for (...) {
- ...
- if (disaster) /* если бедствие */
- goto error; /* уйти на ошибку */
- }
- ...
- error: /* обработка ошибки */
- \textit[ликвидировать беспорядок]
- \end{LongCodeParWithCC}
- \noindent Такая организация программы удобна, если подпрограмма обработки
- ошибочной ситуации не тривиальна и ошибка может встретиться в нескольких местах.
- Метка имеет вид обычного имени переменной, за которым следует двоеточие.
- \index{метка!область действия}%
- \index{область действия!меток}%
- На метку можно перейти с помощью \verb|goto| из любого места данной функции,
- т.е. метка действительна на протяжении всей функции.
- В качестве ещё одного примера рассмотрим такую задачу: определить, есть ли в
- массивах \verb|a| и \verb|b| совпадающие элементы. Один из возможных вариантов
- её реализации имеет следующий вид:
- \begin{LongCodePar}
- for (i = 0; i < n; i++)
- for (j = 0; j < m; j++)
- if (a[i] == b[j])
- goto found;
- /* нет одинаковых элементов */
- ...
- found:
- /* обнаружено совпадение: a[i] == b[j] */
- ...
- \end{LongCodePar}
- Программу нахождения совпадающих элементов можно написать и без \verb|goto|,
- правда, заплатив за это дополнительными проверками и ещё одной переменной:
- \begin{LongCodePar}
- found = 0;
- for (i = 0; i < n && !found; i++)
- for (j = 0; j < m && !found; j++)
- if (a[i] == b[j])
- found = 1;
- if (found)
- /* обнаружено совпадение: a[i-1] == b[j-1] */
- ...
- else
- /* нет одинаковых элементов */
- ...
- \end{LongCodePar}
- За исключением редких случаев, подобных только что приведённым,
- программы с привлечением \verb|goto|, как правило, труднее для понимания и
- сопровождения, чем программы решающие те же задачи без \verb|goto|. Хотя мы и не
- догматики, всё же думается, что к \verb|goto| следует прибегать крайне редко,
- если использовать вообще.
|