\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 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 /* 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 /* 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| следует прибегать крайне редко, если использовать вообще.