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