\chapter{Указатели и массивы} \label{chapt:pointers_and_arrays} \index{указатели!и индексирование}% Указатель -- это переменная, содержащая адрес переменной. Указатели широко применяются в Си -- отчасти потому, что в некоторых случаях без них просто не обойтись, а отчасти потому, что программы с ними обычно короче и эффективнее. Указатели и массивы тесно связаны друг с другом; в этой главе мы рассмотрим эту зависимость и покажем, как ею пользоваться. Наряду с \verb|goto| указатели когда-то были объявлены лучшим средством для написания малопонятных программ. Так оно и есть, если ими пользоваться бездумно. Ведь очень легко получить указатель, ссылающийся на что-нибудь совсем нежелательное. При соблюдении же определённой дисциплины с помощью указателей можно достичь ясности и простоты. Мы попытаемся убедить вас в этом. Изменения, внесённые стандартом ANSI, связаны в основном с формулированием точных правил работы с указателями. Он узаконил накопленный положительный опыт программистов и удачные нововведения разработчиков компиляторов. \index{указатель!void *@\texttt{void *}}% \index{void *, указатель@\texttt{void *}, указатель}% Кроме того, взамен \verb|char *| в качестве типа обобщённого указателя предлагается тип \verb|void *| (указатель на \verb|void|). \section{Указатели и адреса} \index{переменная!адрес}% Начнём с того, что рассмотрим упрощённую схему организации памяти. Память типичной машины представляет собой массив последовательно пронумерованных и проадресованных ячеек, с которыми можно работать по отдельности или связными кусками. Применительно к любой машине верны следующие утверждения: один байт может хранить значение типа \verb|char|, двухбайтовые ячейки могут рассматриваться как целое типа \verb|short|, а четырёхбайтовые -- как целые типа \verb|long|. Указатель -- это группа ячеек (как правило, две или четыре), в которых может храниться адрес. Так, если \verb|c| имеет тип \verb|char|, а \verb|p| -- указатель, ссылающийся на \verb|c|, то ситуация выглядит следующим образом: \begin{figure}[H] \center{\includegraphics[width=0.7831461\linewidth]{chapt5_sec1_img0.eps}} \end{figure} \noindent Унарный оператор \verb|&| выдаёт адрес объекта, так что инструкция \begin{ShortCodePar} p = &c; \end{ShortCodePar} \noindent присваивает адрес ячейки \verb|c| переменной \verb|p| (говорят, что \verb|p| указывает на \verb|c| или, что то же, \verb|p| ссылается на \verb|c|). Оператор \verb|&| применяется только к объектам, расположенным в памяти: к переменным и элементам массивов. Его операндом не может быть ни выражение, ни константа, ни регистровая переменная. \index{оператор!косвенности \texttt{*}}% Унарный оператор \verb|*| есть оператор \emph{раскрытия ссылки}. Применённый к указателю он выдаёт объект, на который данный указатель ссылается. Предположим, что \verb|x| и \verb|y| -- целые, а \verb|ip| -- указатель на \verb|int|. Следующие несколько строк придуманы специально для того, чтобы показать, каким образом декларируются указатели и используются операторы \verb|&| и \verb|*|. \begin{ShortCodePar} int x = 1, y = 2, z[10]; int *ip; /* ip - указатель на int */ ip = &x; /* теперь ip указывает на x */ y = *ip; /* y теперь равен 1 */ *ip = 0; /* x теперь равен 0 */ ip = &z[0]; /* ip теперь указывает на z[0] */ \end{ShortCodePar} \noindent Декларации \verb|x|, \verb|y| и \verb|z| нам уже знакомы. \index{декларация!указателя}% \index{указатель!декларация}% Декларацию указателя \verb|ip| \begin{ShortCodePar} int *ip; \end{ShortCodePar} мы стремились сделать мнемоничной -- она гласит: <<выражение \verb|*ip| есть нечто типа \verb|int|>>. Синтаксис декларации переменной <<подстраивается>> под синтаксис выражений, в которых эта переменная может встретиться. Указанный принцип применим и в отношении описаний функций. Например, запись \begin{ShortCodePar} double *dp, atof(char *); \end{ShortCodePar} \noindent означает, что выражение \verb|*dp| и \verb|atof(s)| имеют тип \verb|double|‚ а аргумент функции \verb|atof| есть указатель на \verb|char|. Вы, наверное, заметили, что указателю разрешено ссылаться только на объекты заданного типа. (Существует одно исключение: <<указатель на \verb|void|>> может ссылаться на объекты любого типа, но к такому указателю нельзя применять оператор раскрытия ссылки. Мы вернёмся к этому в разд.~\ref{sec:function_pointers}.) Если \verb|ip| ссылается на \verb|x| целого типа, то \verb|*ip| можно использовать в любом месте, где допустимо применение \verb|x|; например, \begin{ShortCodePar} *ip = *ip + 10; \end{ShortCodePar} \noindent увеличивает \verb|*ip| на $10$. \index{вычисление, порядок}% \index{оператор!приоритет}% \index{порядок!вычислений}% \index{приоритеты операторов}% Унарные операторы \verb|*| и \verb|&| имеют более высокий приоритет, чем арифметические операторы, так что присваивание \begin{ShortCodePar} y = *ip + 1 \end{ShortCodePar} \noindent берет то, на что указывает \verb|ip|, и добавляет к нему $1$, а результат присваивает переменной \verb|y|. Аналогично \begin{ShortCodePar} *ip += 1 \end{ShortCodePar} \noindent увеличивает на единицу то, на что ссылается \verb|ip|; те же действия выполняют \begin{ShortCodePar} ++*ip \end{ShortCodePar} \noindent и \begin{ShortCodePar} (*ip)++ \end{ShortCodePar} \noindent% \index{указатели!арифметика с}% В последней записи скобки необходимы, поскольку, если их не будет, увеличится значение самого указателя, а не то, на что он ссылается. Это обусловлено тем, что унарные операторы \verb|*| и \verb|++| имеют одинаковые приоритет и порядок выполнения -- справа налево. И наконец, так как указатели сами являются переменными, в тексте они могут встречаться и без оператора раскрытия ссылки. Например, если \verb|iq| есть другой указатель на \verb|int|, то \begin{ShortCodePar} iq = ip \end{ShortCodePar} \noindent копирует содержимое \verb|ip| в \verb|iq|, чтобы \verb|ip| и \verb|iq| ссылались на один и тот же объект. \section{Указатели и аргументы функций} \index{вызов!по значению}% Поскольку функции в Си в качестве своих аргументов получают значения параметров, прямой возможности, находясь в вызванной функции, изменить переменную вызывающей функции нет. В программе сортировки нам понадобилась функция \verb|swap|, переставляющая местами два неупорядоченных элемента. Однако недостаточно написать \begin{ShortCodePar} swap(a, b); \end{ShortCodePar} \noindent где функция \verb|swap| определена следующим образом: \begin{LongCodePar} void swap(int x, int y) /* НЕВЕРНО */ { int temp; temp = x; x = y; y = temp; } \end{LongCodePar} \noindent Поскольку \verb|swap| получает лишь \emph{копии} значений переменных \verb|a| и \verb|b|, она не может повлиять на переменные \verb|a| и \verb|b| той программы, которая к ней обратилась. \index{вызов!по ссылке}% \index{указатель!аргумент}% Чтобы получить желаемый эффект, надо вызывающей программе передать \emph{указатели} на те значения, которые должны быть изменены: \begin{ShortCodePar} swap(&a, &b); \end{ShortCodePar} \noindent Так как оператор \verb|&| получает адрес переменной, \verb|&a| есть указатель на \verb|a|. \index{функция!swap@\texttt{swap}}% В самой функции \verb|swap| параметры должны быть описаны как указатели, при этом доступ к значениям параметров будет осуществляться через них косвенно. \begin{LongCodePar} void swap(int *px, int *py) /* перестановка *px и *py */ { int temp; temp = *px; *px = *py; *py = temp; } \end{LongCodePar} \noindent Графически это можно изобразить следующим образом: \begin{figure}[H] \center{\includegraphics[width=0.3389831\linewidth]{chapt5_sec2_img0.eps}} \end{figure} Аргументы-указатели позволяют функции осуществлять доступ к объектам вызвавшей её программы и дают ей возможность изменить эти объекты. \index{функция!getint@\texttt{getint}}% Рассмотрим, например, функцию \verb|getint|, которая осуществляет ввод в свободном формате одного целого числа и его перевод из текстового представления в значение типа \verb|int|. Функция \verb|getint| должна возвращать значение полученного числа или сигнализировать значением \verb|EOF| о конце файла, если входной поток исчерпан. Эти значения должны возвращаться по разным каналам, так как нельзя рассчитывать на то, что полученное в результате перевода число никогда не совпадёт с \verb|EOF|. Одно из решений состоит в том, чтобы \verb|getint| выдавала характеристику состояния файла (исчерпан или не исчерпан) в качестве результата, а значение самого числа помещала согласно указателю, переданному ей в виде аргумента. Похожая схема действует в программе \verb|scanf|, которую мы рассмотрим в разд.~\ref{sec:formatted-input}. Показанный ниже цикл заполняет некоторый массив целыми числами, полученными с помощью \verb|getint|. \begin{ShortCodePar} int n, array[SIZE], getint(int *); for (n = 0; n < SIZE && getint(&array[n]) != EOF; n++) ; \end{ShortCodePar} \noindent Результат каждого очередного обращения к \verb|getint| посылается в \verb|array[n]|, и \verb|n| увеличивается на единицу. Заметим, что существенным здесь является то, что функции \verb|getint| передаётся адрес элемента \verb|array[n]|. Если этого не сделать, у \verb|getint| не будет способа вернуть в вызывающую программу переведённое целое число. В предлагаемом нами варианте функция \verb|getint| выдаёт \verb|EOF| по концу файла; нуль, если следующие вводимые литеры не представляют собою числа; и положительное значение, если введённые литеры есть правильное число. \begin{LongCodePar} #include int getch(void); void ungetch(int); /* getint: читает следующее целое из ввода в *pn */ int getint(int *pn) { int c, sign; while (isspace(c = getch())) ; /* пропуск пробельных литер */ if (!isdigit(c) && c != EOF && c != '+' && c != '-') { ungetch(c); /* не число */ return 0; } sign = (c == '-') ? -1 : 1; if (c == '+' || c == '-') c = getch(); for (*pn = 0; isdigit(c); c = getch()) *pn = 10 * *pn + (c - '0'); *pn *= sign; if (c != EOF) ungetch(c); return c; } \end{LongCodePar} % % в оригинале слева в нижнем колонтитуле надпись ``4. Заказ № 13'' % \noindent Везде в \verb|getint| под комбинацией \verb|*pn| подразумевается обычная переменная типа \verb|int|. Функция \verb|ungetch| вместе с \verb|getch| (разд.~\ref{sec:external_variables}) включена в программу, чтобы обеспечить возможность отослать назад лишнюю прочитанную литеру. % % исправлена опечатка. В оригинале ``влючена в программу'' % \paragraph{Упражнение 5.1.} Функция \verb|getint| написана так, что знаки \verb|-| или \verb|+|, за которыми не следует цифра, она понимает как <<правильное>> представление нуля. Скорректируйте программу таким образом, чтобы она в подобных случаях возвращала прочитанный знак назад во ввод. \paragraph{Упражнение 5.2.} Напишите функцию \verb|getfloat| -- аналог \verb|getint| для чисел с плавающей точкой. Какой тип будет иметь результирующее значение, выдаваемое функцией \verb|getfloat|? \section{Указатели и массивы} \index{индексирование массива!и указатели|(}% \index{массив!а не указатель|(}% \index{указатель!а не массив|(}% В Си существует связь между указателями и массивами, и связь эта настолько тесная, что эти средства лучше рассматривать вместе. \index{индексирование массива}% \index{массив!индексирование}% Любой доступ к элементу массива, осуществляемый операцией индексирования, может быть выполнен при помощи указателя. Вариант с указателями в общем случае работает быстрее, но разобраться в нём, особенно непосвящённому, довольно трудно. Декларация \begin{ShortCodePar} int a[10]; \end{ShortCodePar} \noindent определяет массив \verb|a| размера $10$, т.е. блок из $10$ последовательных объектов с именами \verb|a[0]|‚ \verb|a[1]|‚ \ldots ‚ \verb|a[9]|. \begin{figure}[H] \center{\includegraphics[width=0.7865169\linewidth]{chapt5_sec3_img0.eps}} \end{figure} \noindent Запись \verb|a[i]| отсылает нас к \verb|i|-му элементу массива. Если \verb|pa| есть указатель на \verb|int|, т.е. определён как \begin{ShortCodePar} int *pa; \end{ShortCodePar} \noindent то в результате присваивания \begin{ShortCodePar} pa = &a[0]; \end{ShortCodePar} \noindent \verb|pa| будет указывать на нулевой элемент \verb|a|; иначе говоря, \verb|pa| будет содержать адрес элемента \verb|a[0]|. \begin{figure}[H] \center{\includegraphics[width=0.8651685\linewidth]{chapt5_sec3_img1.eps}} \end{figure} \noindent Теперь присваивание \begin{ShortCodePar} x = *pa; \end{ShortCodePar} \noindent будет копировать содержимое \verb|a[0]| в \verb|x|. \index{указатели!арифметика с}% Если \verb|pa| указывает на некоторый элемент массива, то \verb|pa+1| по определению указывает на следующий элемент, \verb|pa+i| -- на \verb|i|-й элемент после \verb|pa|, а \verb|pa-i| -- на \verb|i|-й элемент перед \verb|pa|. Таким образом, если \verb|pa| указывает на \verb|a[0]|, то \begin{ShortCodePar} *(pa+1) \end{ShortCodePar} \noindent есть содержимое \verb|a[1]|, \verb|pa+i| -- адрес \verb|a[i]|, а \verb|*(pa+i)| -- содержимое \verb|a[i]|. \begin{figure}[H] \center{\includegraphics[width=0.8651685\linewidth]{chapt5_sec3_img2.eps}} \end{figure} Сделанные замечания верны безотносительно к типу и размеру элементов массива \verb|a|. Смысл слов <<добавить $1$ к указателю>>, как и смысл любой арифметики с указателями, в том, чтобы \verb|pa+1| указывал на следующий объект, а \verb|pa+i| -- на \verb|i|-й после \verb|pa|. Между индексированием и арифметикой с указателями существует очень тесная связь. По определению значение переменной или выражения типа массив есть адрес нулевого элемента массива. После присваивания \begin{ShortCodePar} pa = &a[0]; \end{ShortCodePar} \noindent \verb|pa| и \verb|a| имеют одно и то же значение. Поскольку имя массива есть не что иное, как адрес его начального элемента, присваивание \verb|pa=&a[0];| можно также записать в следующем виде: \begin{ShortCodePar} pa = a; \end{ShortCodePar} Ещё более удивительно (по крайней мере на первый взгляд) то, что \verb|a[i]| можно записать как \verb|*(a+i)|. \index{массив!преобразование имени}% \index{преобразование!имени массива}% Встречая запись \verb|a[i]|, компилятор сразу преобразует её в \verb|*(a+i)|; указанные две формы записи эквивалентны. Из этого следует, что полученные в результате применения оператора \verb|&| записи \verb|&a[i]| и \verb|a+i| также будут эквивалентными, т.е. и в том и в другом случае это адрес \verb|i|-го элемента после \verb|a|. С другой стороны, если \verb|pa| -- указатель, то в выражениях его можно использовать с индексом, т.е. запись \verb|pa[i]| эквивалентна записи \verb|*(pa+i)|. Элемент массива одинаково разрешается изображать и в виде указателя со смещением и в виде имени массива с индексом. Между именем массива и указателем, выступающим в роли имени массива, существует одно различие. Указатель -- это переменная, поэтому можно написать \verb|pa = a| или \verb|pa++|. Но имя массива не является переменной, и записи типа \verb|a = pa| или \verb|a++| не допускаются. \index{массив!имя в роли аргумента}% \index{указатель!аргумент}% Если имя массива передаётся функции, то последняя получает в качестве аргумента адрес его начального элемента. Внутри вызываемой функции этот аргумент является локальной переменной, содержащей адрес. \index{стринг!длина}% \index{функция!strlen@\texttt{strlen}}% Мы можем воспользоваться отмеченным фактом и написать ещё одну версию функции \verb|strlen|, вычисляющей длину стринга. \begin{LongCodePar} /* strlen: возвращает длину стринга */ int strlen(char *s) { int n; for (n = 0; *s != '\0'; s++) n++; return n; } \end{LongCodePar} \noindent Так как переменная \verb|s| -- указатель, к нему применима операция \verb|++|; \verb|s++| никакого влияния на стринг литер функции, которая обратилась к \verb|strlen|‚ не оказывает. Просто увеличивается на $1$ некоторая копия указателя, находящаяся в личном пользовании функции \verb|strlen|. Это значит, что все вызовы типа: \begin{ShortCodePar} strlen("Здравствуй, мир"); /* стринговая константа */ strlen(array); /* char array[100]; */ strlen(ptr); /* char *ptr; */ \end{ShortCodePar} \noindent законны. \index{декларация!указателя}% \index{параметр}% \index{указатель!декларация}% Записи \begin{ShortCodePar} char s[]; \end{ShortCodePar} \noindent и \begin{ShortCodePar} char *s; \end{ShortCodePar} \noindent в определении функции в качестве формальных параметров эквивалентны. Мы отдаём предпочтение последней, поскольку она более явно сообщает, что \verb|s| есть указатель. \index{массив!имя в роли аргумента}% \index{указатель!аргумент}% Если функции в качестве аргумента передаётся имя массива, то она может рассматривать его так, как ей удобно -- либо как имя массива, либо как указатель, и поступать с ним соответственно. Она может даже использовать оба вида записи, если это покажется ей уместным. \index{подмассив-аргумент}% \index{указатель!аргумент}% Функции можно передать часть массива, для этого аргумент должен указывать на начало подмассива. Например, если \verb|a| -- массив, то в записях \begin{ShortCodePar} f(&a[2]) \end{ShortCodePar} \noindent или \begin{ShortCodePar} f(a+2) \end{ShortCodePar} \noindent функции \verb|f| передаётся адрес подмассива, начинающегося с элемента \verb|a[2]|. Внутри функции \verb|f| описание параметров может выглядеть как \begin{ShortCodePar} f(int arr[]) { ... } \end{ShortCodePar} \noindent или \begin{ShortCodePar} f(int *arr) { ... } \end{ShortCodePar} \noindent Следовательно, для \verb|f| тот факт, что параметр ссылается на часть массива, а не на весь массив, не имеет значения. \index{индекс отрицательный}% \index{отрицательные индексы}% \index{указатели!и индексирование}% Если есть уверенность, что элементы массива существуют, то возможно индексирование и в <<обратную>> сторону по отношению к нулевому элементу; выражения \verb|p[-1]|, \verb|p[-2]| и т.д. не противоречат синтаксису языка и ссылаются на элементы, стоящие непосредственно перед \verb|p[0]|. Разумеется, нельзя <<выходить>> за границы массива и тем самым ссылаться на несуществующие <<объекты>>. \index{указатель!а не массив|)}% \index{массив!а не указатель|)}% \index{индексирование массива!и указатели|)}% \section{Адресная арифметика} \index{указатели!арифметика с|(}% Если \verb|p| есть указатель на некоторый элемент массива, то \verb|p++| продвигает \verb|p| так, чтобы он указывал на следующий элемент, а \verb|p += i| увеличивает его, чтобы он указывал на \verb|i|-й элемент после того, на который он указывал ранее. Эти и подобные конструкции -- самые простые примеры арифметики над указателями, называемой также адресной арифметикой. Си последователен и единообразен в своём подходе к адресной арифметике. Это соединение в одном языке указателей, массивов и адресной арифметики -- одна из сильных его сторон. Проиллюстрируем сказанное построением простого распределителя памяти, состоящего из двух программ. \index{функция!alloc@\texttt{alloc}}% Первая, \verb|alloc(n)|‚ возвращает указатель \verb|p| на \verb|n| последовательно расположенных ячеек типа \verb|char|; программой, обращающейся к \verb|alloc|, эти ячейки могут быть использованы для запоминания литер. \index{функция!afree@\texttt{afree}}% Вторая, \verb|afree(p)|‚ освобождает память для, возможно, повторной её утилизации. Простота алгоритма обусловлена предположением, что обращения к \verb|afree| делаются в обратном порядке по отношению к соответствующим обращениям к \verb|alloc|. Таким образом, память, с которой работают \verb|alloc| и \verb|afree|, является стеком (списком, в основе которого лежит принцип <<последним вошёл, первым ушёл>>). В стандартной библиотеке имеются функции \verb|malloc| и \verb|free|, которые делают то же самое, только без упомянутых ограничений; в разд.~\ref{sec:memory_allocator} мы покажем, как они выглядят. Функцию \verb|alloc| легче всего реализовать, если условиться, что она будет выдавать куски некоторого большого массива типа \verb|char|, который мы назовём \verb|allocbuf|. Этот массив отдадим в личное пользование функциям \verb|alloc| и \verb|afree|. Так как они имеют дело с указателями, а не с индексами массива, то другим программам знать его имя не нужно. Кроме того, этот массив можно определить в том же исходном файле, что и \verb|alloc| и \verb|afree|, объявив его с классификатором \verb|static|, благодаря чему он станет невидимым вне этого файла. На практике такой массив может и вовсе не иметь имени, поскольку его можно запросить с помощью \verb|malloc| у операционной системы и получить указатель на некоторый безымянный блок памяти. Естественно, нам нужно знать, сколько элементов массива \verb|allocbuf| уже занято. Мы введём указатель \verb|allocp|, который будет указывать на первый свободный элемент. Если запрашивается память для \verb|n| литер, то \verb|alloc| возвращает текущее значение \verb|allocp| (т.е. адрес начала свободного блока) и затем увеличивает его на \verb|n|, чтобы указатель \verb|allocp| ссылался на следующую свободную область. Если же пространства нет, то \verb|alloc| выдаёт нуль. Функция \verb|afree(p)| просто устанавливает в \verb|allocp| значение \verb|p|, если оно не выходит за пределы массива \verb|allocbuf|. \begin{figure}[H] \center{\includegraphics[width=0.8926554\linewidth]{chapt5_sec4_img0.eps}} \end{figure} \begin{LongCodePar} #define ALLOCSIZE 10000 /* размер доступного пространства */ static char allocbuf[ALLOCSIZE]; /* память для alloc */ static char *allocp = allocbuf; /* ук-ль на своб. место */ char *alloc(int n) /* возвращает указатель на n литер */ { if (allocbuf + ALLOCSIZE - allocp >= n) { allocp += n; /* пространство есть */ return allocp - n; /* старое p */ } else /* пространства нет */ return 0; } void afree(char *p) /* освобождается память по ук-лю p */ { if (p >= allocbuf && p < allocbuf + ALLOCSIZE) allocp = p; } \end{LongCodePar} \index{инициализация!указателя}% \index{указатель!инициализация}% \index{указатель!пустой}% Как и любую другую переменную, указатель можно инициализировать, но только такими осмысленными для него значениями, как нуль или выражение, приводящее к некоторому адресу ранее определённых данных соответствующего типа. Декларация \begin{ShortCodePar} static char *allocp = allocbuf; \end{ShortCodePar} \noindent определяет \verb|allocp| как указатель на \verb|char| и инициализирует его адресом массива \verb|allocbuf|, поскольку перед началом работы программы массив \verb|allocbuf| пуст. Указанная декларация могла бы иметь и такой вид: \begin{ShortCodePar} static char *allocp = &allocbuf[0]; \end{ShortCodePar} \noindent поскольку имя массива и есть адрес его нулевого элемента. Проверка \begin{ShortCodePar} if (allocbuf + ALLOCSIZE - allocp >= n) { \end{ShortCodePar} \noindent контролирует, достаточно ли пространства, чтобы удовлетворить запрос на \verb|n| литер. Если памяти достаточно, то новое значение для \verb|allocp| должно указывать не далее чем на следующую позицию за последним элементом \verb|allocbuf|. При выполнении этого требования \verb|alloc| выдаёт указатель на начало выделенного блока литер (обратите внимание на описание типа самой функции). Если требование не выполняется, функция \verb|alloc| должна выдать какой-то сигнал о том, что памяти не хватает. Си гарантирует, что нуль никогда не будет правильной ссылкой на данные, поэтому мы будем использовать его в качестве признака аварийного события, в нашем случае нехватки памяти. \index{неправильная арифметика с указателями}% \index{указатели!неправильная арифметика с}% \index{указатели!операция над}% \index{указатели!сравнение}% Указатели и целые не являются взаимозаменяемыми объектами. \index{указатель!пустой}% \index{null-указатель}% Константа нуль -- единственное исключение из этого правила: её можно присвоить указателю, и указатель можно сравнить с нулевой константой. \index{файл!головной!@\texttt{}}% \index{NULL@\texttt{NULL}}% \index{stdio.h@\texttt{}}% Чтобы показать, что нуль -- это специальное значение для указателя, вместо цифры нуль, как правило, записывают \verb|NULL| -- константу, определённую в файле \verb||. С этого момента и мы будем ею пользоваться. Проверки \begin{ShortCodePar} if (allocbuf + ALLOCSIZE - allocp >= n) { \end{ShortCodePar} \noindent и \begin{ShortCodePar} if (p >= allocbuf && p < allocbuf + ALLOCSIZE) \end{ShortCodePar} \noindent демонстрируют несколько важных свойств арифметики с указателями. \index{операции над!указателями}% \index{сравнение указателей}% Во-первых, при соблюдении некоторых правил указатели можно сравнивать. Если \verb|p| и \verb|q| указывают на элементы одного массива, то к ним можно применять операторы отношения \verb|==|, \verb|!=|, \verb|<|, \verb|>=| и т.д. Например, отношение вида \begin{ShortCodePar} p < q \end{ShortCodePar} \noindent истинно, если \verb|p| указывает на более ранний элемент массива, чем \verb|q|. Любой указатель всегда можно сравнить на равенство и неравенство с нулём. А вот для указателей, ссылающихся на элементы разных массивов, результат арифметических операций или сравнений не определён. (Существует одно исключение: в арифметике с указателями можно использовать адрес несуществующего <<следующего за массивом>> элемента, т.е. адрес того <<элемента>>, который станет последним, если в массив добавить ещё один элемент.) Во-вторых, как вы уже, наверное, заметили, указатели и целые можно складывать и вычитать. Запись вида \begin{ShortCodePar} p + n \end{ShortCodePar} \noindent означает адрес объекта, занимающего \verb|n|-е место после объекта, на который указывает \verb|p|. \index{масштабирование целых в арифметике с указателями}% \index{указатели!коэффициент домножения целых в арифметике с}% Это справедливо безотносительно к типу объекта, на который ссылается \verb|p|; \verb|n| автоматически домножается на коэффициент, соответствующий размеру объекта. Информация о размере неявно присутствует в описании \verb|p|. Если, к примеру, \verb|int| занимает четыре байта, то коэффициент умножения будет равен четырём. \index{вычитание из указателя}% \index{указатели!вычитание}% Допускается также вычитание указателей. Например, если \verb|p| и \verb|q| ссылаются на элементы одного массива и \verb|p < q|, то \verb|q - p + 1| есть число элементов от \verb|p| до \verb|q| включительно. \index{стринг!длина}% \index{функция!strlen@\texttt{strlen}}% Этим фактом можно воспользоваться при написании ещё одной версии \verb|strlen|: \begin{LongCodePar} /* strlen: возвращает длину стринга s */ int strlen(char *s) { char *p = s; while (*p != '\0') p++; return p - s; } \end{LongCodePar} \noindent В своём определении \verb|p| инициализируется значением \verb|s|, т.е. вначале \verb|p| указывает на первую литеру стринга. На каждом шаге цикла \verb|while| проверяется очередная литера; цикл продолжается до тех пор, пока не встретится \verb|'\0'|. Каждое продвижение указателя \verb|p| на следующую литеру выполняется инструкцией \verb|p++|, и разность \verb|p-s| даёт число пройденных литер, т.е. длину стринга. (Число литер в стринге может быть слишком большим, чтобы хранить его в переменной типа \verb|int|. \index{файл!головной!@\texttt{}}% \index{ptrdiff{\_}t@\texttt{ptrdiff{\_}t}}% Тип \verb|ptrdiff_t|, достаточный для хранения разности (со знаком) двух указателей, определён в головном файле \verb||. \index{size{\_}t@\texttt{size{\_}t}}% Однако, если быть очень осторожными, нам следовало бы для возвращаемого результата использовать тип \verb|size_t|‚ в этом случае наша программа соответствовала бы стандартной библиотечной версии. \index{оператор!sizeof@\texttt{sizeof}}% Тип \verb|size_t| есть тип беззнакового целого, возвращаемого оператором \verb|sizeof|.) \index{неправильная арифметика с указателями}% \index{указатели!коэффициент домножения целых в арифметике с}% \index{указатели!неправильная арифметика с}% Арифметика с указателями учитывает тип: если она имеет дело со значениями \verb|float|, занимающими больше памяти, чем \verb|char|, и \verb|p| -- указатель на \verb|float|, то \verb|p++| продвинет \verb|p| на следующее значение \verb|float|. Это значит, что другую версию \verb|alloc|, которая имеет дело с элементами типа \verb|float|, а не \verb|char|, можно получить простой заменой в \verb|alloc| и \verb|afree| всех \verb|char| на \verb|float|. Все операции с указателями будут автоматически откорректированы в соответствии с размером объектов, на которые ссылаются указатели. \index{неправильная арифметика с указателями}% \index{указатели!неправильная арифметика с}% Допускаются следующие операции с указателями: присваивание значения указателя другому указателю того же типа, сложение и вычитание указателя и целого, вычитание и сравнение двух указателей, ссылающихся на элементы одного и того же массива, а также присваивание указателю нуля и сравнение указателя с нулём. Все другие операции с указателями не допускаются. Нельзя складывать два указателя, перемножать их, делить, сдвигать, выделять разряды; указатель нельзя складывать со значением типа \verb|float| или \verb|double|; \index{указатель!void *@\texttt{void *}}% \index{void *, указатель@\texttt{void *}, указатель}% указателю одного типа нельзя даже присвоить указатель другого типа, не выполнив предварительно операции приведения (исключение составляют лишь указатели типа \verb|void *|). \index{указатели!арифметика с|)}% \section{Литерные указатели и функции} \index{массив!литер}% \index{константа!стринговая}% \emph{Стринговая константа}, написанная в виде \begin{ShortCodePar} "I am a string" \end{ShortCodePar} \noindent есть массив литер. Во внутреннем представлении этот массив заканчивается <<пустой>> литерой \verb|'\0'|, по которой программа может найти конец стринга. Число занятых ячеек памяти на одну больше, чем количество литер, помещённых между двойными кавычками. Чаще всего стринговые константы используются в качестве аргументов функций, как, например, в \begin{ShortCodePar} printf("здравствуй, мир\n"); \end{ShortCodePar} % % в оригинале опечатка ``здраствуй'' % \noindent Когда такой литерный стринг появляется в программе, доступ к нему осуществляется через литерный указатель; \verb|printf| получает указатель на начало массива литер. Точнее, доступ к стринговой константе осуществляется через указатель на её первый элемент. Стринговые константы нужны не только в качестве аргументов функций. Если, например, переменную \verb|pmessage| описать как \begin{ShortCodePar} char *pmessage \end{ShortCodePar} \noindent то присваивание \begin{ShortCodePar} pmessage = "now is the time"; \end{ShortCodePar} \noindent поместит в неё указатель на литерный массив, при этом сам стринг \emph{не} копируется, копируется лишь указатель на него. Операции для работы со стрингом как с единым целым в Си не предусмотрены. \index{массив!а не указатель}% \index{указатель!а не массив}% Существует важное различие между следующими определениями: \begin{ShortCodePar} char amessage[] = "now is the time"; /* массив */ char *pmessage = "now is the time"; /* указатель */ \end{ShortCodePar} \noindent \verb|amessage| -- это массив, имеющий такой объем, что в нём как раз помещается указанная последовательность литер и \verb|'\0'|. Отдельные литеры внутри массива могут изменяться, но \verb|amessage| всегда ссылается на одно и то же место памяти. В противоположность ему \verb|pmessage| есть указатель, инициализированный ссылкой на стринговую константу. А значение указателя можно изменить, и тогда последний будет ссылаться на что-либо другое. Кроме того, результат будет неопределён, если вы попытаетесь изменить содержимое константы. \begin{figure}[H] \center{\includegraphics[width=0.6629213\linewidth]{chapt5_sec5_img0.eps}} \end{figure} % % в картинке исправлены опечатки - неверное экранирование символа \'0' % \index{массив!а не указатель}% \index{указатель!а не массив}% Дополнительные моменты, связанные с указателями и массивами, проиллюстрируем на несколько видоизменённых вариантах двух полезных программ, взятых нами из стандартной библиотеки. Первая из них, функция \verb|strcpy(s,t)|, копирует стринг \verb|t| в стринг \verb|s|. Хотелось бы написать прямо \verb|s=t|‚ но такой оператор копирует указатель, а не литеры. Чтобы копировать литеры, нам нужно организовать цикл. \index{функция!strcpy@\texttt{strcpy}}% Первый вариант \verb|strcpy|, с использованием массива, имеет следующий вид: \begin{LongCodePar} /* strcpy: копирует t в s; вариант с индексируемым массивом */ void strcpy(char *s, char *t) { int i; i = 0; while ((s[i] = t[i]) != '\0') i++; } \end{LongCodePar} \noindent% \index{функция!strcpy@\texttt{strcpy}}% Для сравнения приведём версию \verb|strcpy| с указателями: \begin{LongCodePar} /* strcpy: копирует t в s; версия 1 (с указателями) */ void strcpy(char *s, char *t) { while ((*s = *t) != '\0') { s++; t++; } } \end{LongCodePar} \noindent Поскольку передаются лишь копии значений аргументов, \verb|strcpy| может свободно пользоваться параметрами \verb|s| и \verb|t| как своими локальными переменными. Они должным образом инициализированы указателями, которые продвигаются каждый раз на следующую литеру в каждом из массивов до тех пор, пока в копируемом стринге \verb|t| не встретится \verb|'\0'|. \index{функция!strcpy@\texttt{strcpy}}% На практике \verb|strcpy| так не пишут. Опытный программист предпочтёт более короткую запись: \begin{ShortCodePar} /* strcpy: копирует t в s; версия 2 (с указателями) */ void strcpy(char *s, char *t) { while ((*s++ = *t++) != '\0') ; } \end{ShortCodePar} \noindent Продвижение \verb|s| и \verb|t| здесь осуществляется в управляющей части цикла. \index{операторы!постфиксные \texttt{++} и \texttt{\textminus\textminus}}% Значением \verb|*t++| является литера, на которую указывает переменная \verb|t| перед тем, как её значение будет продвинуто; постфиксный оператор \verb|++| не изменяет указатель \verb|t|, пока не будет взята литера, на которую он указывает. То же в отношении \verb|s|, сначала литера запомнится в позиции, на которую указывает старое значение \verb|s|, и лишь после этого значение переменной \verb|s| увеличится. Пересылаемая литера является одновременно и значением, которое сравнивается с \verb|'\0'|. В итоге копируются все литеры, включая и заключительную литеру \verb|'\0'|. \index{нуль, опущенная проверка на него}% \index{функция!strcpy@\texttt{strcpy}}% Заметив, что сравнение с \verb|'\0'| здесь лишнее (поскольку в Си ненулевое значение выражения в условии трактуется и как его истинность), мы можем сделать ещё одно и последнее сокращение текста программы: \begin{ShortCodePar} /* strcpy: копирует t в s; версия 3 (с указателями) */ void strcpy(char *s, char *t) { while (*s++ = *t++) ; } \end{ShortCodePar} \noindent Хотя на первый взгляд то, что мы получили, выглядит как криптограмма, всё же такая запись значительно удобнее, и следует освоить её, поскольку в Си-программах вы будете с ней часто встречаться. \index{файл!головной!@\texttt{}}% Что касается функции \verb|strcpy| из стандартной библиотеки \verb||‚ то она возвращает в качестве своего результата ещё и ссылку на новую копию стринга. \index{функция!strcmp@\texttt{strcmp}}% Вторая программа, которую мы здесь рассмотрим, это \verb|strcmp(s,t)|. Она сравнивает литеры стрингов \verb|s| и \verb|t| и возвращает отрицательное, нулевое или положительное значение, если стринг \verb|s| соответственно лексикографически меньше, равен или больше, чем стринг \verb|t|. Результат получается вычитанием первых несовпадающих литер из \verb|s| и \verb|t|. \begin{LongCodePar} /* strcmp: выдаёт <0 при s0 при s>t */ int strcmp(char *s, char *t) { int i; for (i = 0; s[i] == t[i]; i++) if (s[i] == '\0') return 0; return s[i] - t[i]; } \end{LongCodePar} \noindent Та же программа с использованием указателей записывается так: \begin{LongCodePar} /* strcmp: выдаёт <0 при s0 при s>t */ int strcmp(char *s, char *t) { for ( ; *s == *t; s++, t++) if (*s == '\0') return 0; return *s - *t; } \end{LongCodePar} \index{оператор!инкрементации \texttt{++}}% \index{оператор!декрементации \texttt{\textminus\textminus}}% \index{операторы!постфиксные \texttt{++} и \texttt{\textminus\textminus}}% \index{операторы!префиксные \texttt{++} и \texttt{\textminus\textminus}}% Поскольку операторы \verb|++| и \verb|--| могут быть и префиксными, и постфиксными, возможны (хотя встречаются и не так часто) другие их сочетания с оператором \verb|*|. Например, \begin{ShortCodePar} *--p \end{ShortCodePar} \noindent уменьшит \verb|p| прежде, чем по этому указателю будет получена литера. Например, следующие два выражения: \begin{ShortCodePar} *p++ = val; /* поместить val в стек */ val = *--p; /* взять из стека значение и поместить в val */ \end{ShortCodePar} \noindent являются стандартными записями для посылки в стек и взятия из стека.~(См. разд.~\ref{sec:external_variables}) \index{файл!головной!@\texttt{}}% Описания функций, упомянутых в этом разделе, а также ряда других стандартных функций, работающих со стрингами, содержатся в головном файле \verb||. \paragraph{Упражнение 5.3.} Используя указатели, напишите функцию \verb|strcat|, которую мы рассматривали в гл.~\ref{chapt:types-operators-expressions} (функция \verb|strcat(s,t)| копирует стринг \verb|t| в конец стринга \verb|s|). \paragraph{Упражнение 5.4.} Напишите функцию \verb|strend(s,t)|, которая выдаёт $1$, если стринг \verb|t| расположен в конце стринга \verb|s|, и нуль в противном случае. \paragraph{Упражнение 5.5.} Напишите варианты библиотечных функций \verb|strncpy|, \verb|strncat| и \verb|strncmp|, которые оперируют с первыми литерами своих аргументов, число которых не превышает \verb|n|. Например, \verb|strncpy(t,s,n)| копирует не более \verb|n| литер \verb|t| в \verb|s|. Полные описания этих функций содержатся в приложении~\ref{apx:stdlib}. \paragraph{Упражнение 5.6.} Отберите подходящие программы из предыдущих глав и упражнений и перепишите их, используя вместо индексирования указатели. Подойдут, в частности, программы \verb|getline|~(гл.~\ref{chapt:tutorial_intro}~и~\ref{chapt:functions_and_program_structure}), \verb|atoi|, \verb|itoa| и их варианты~(гл.~\ref{chapt:types-operators-expressions},~\ref{chapt:control_flow}~и~\ref{chapt:functions_and_program_structure}), \verb|reverse|~(гл.~\ref{chapt:control_flow}), а также \verb|strindex| и \verb|getop|~(гл.~\ref{chapt:functions_and_program_structure}). \section{Массивы указателей, указатели на указатели} \label{sec:arrays_of_pointers} \index{массив!указателей}% \index{указатели!массив из}% Как и любые другие переменные, указатели можно группировать в массивы. \index{программа!сортировки}% \index{сортировка!текстовых строк}% Для иллюстрации этого напишем программу, сортирующую в алфавитном порядке текстовые строки; это будет упрощённый вариант программы \verb|sort| системы UNIX. В гл.~\ref{chapt:control_flow} мы привели функцию сортировки по Шеллу, которая упорядочивает массив целых, а в гл.~\ref{chapt:functions_and_program_structure} улучшили её, повысив быстродействие. Те же алгоритмы используются и здесь, однако теперь они будут обрабатывать текстовые строки, которые могут иметь разную длину и сравнение или перемещение которых невозможно выполнить за одну операцию. Нам необходимо выбрать некоторое представление данных, которое бы позволило удобно и эффективно работать с текстовыми строками произвольной длины. \index{сортировка!лексикографическая}% Для этого воспользуемся массивом указателей на начала строк. Поскольку строки в памяти расположены вплотную друг к другу, к каждой отдельной строке доступ просто осуществлять через указатель на её первую литеру. Сами указатели можно организовать в виде массива. Одна из возможностей сравнить две строки -- передать указатели на них функции \verb|strcmp|. Чтобы поменять местами строки, достаточно будет поменять местами в массиве их указатели (а не сами строки). \begin{figure}[H] \center{\includegraphics[width=0.8651685\linewidth]{chapt5_sec6_img0.eps}} \end{figure} \noindent Здесь снимаются сразу две проблемы: одна -- связанная со сложностью управления памятью, а вторая -- с большими накладными расходами при перестановках самих строк. \index{модульность}% Процесс сортировки распадается на три этапа: \begin{ShortCodeParWithCC}{\\\{\}} \textit{чтение всех строк из ввода} \textit{сортировка введённых строк} \textit{печать их по порядку} \end{ShortCodeParWithCC} \noindent Как обычно, выделим функции, соответствующие естественному делению задачи, и напишем главную программу, управляющую этими функциями. Отложим на время реализацию этапа сортировки и сосредоточимся на структуре данных и вводе-выводе. Программа ввода должна прочитать и запомнить литеры всех строк, а также построить массив указателей на строки. Она, кроме того, должна подсчитать число введённых строк -- эта информация понадобится для сортировки и печати. Так как функция ввода может работать только с конечным числом строк, то, если их введено слишком много, она будет выдавать некоторое значение, которое никогда не совпадёт ни с каким количеством строк, например, $-1$. Программа вывода занимается только тем, что печатает строки, причём в том порядке, в котором в массиве указателей на них расположены ссылки. \index{функция!readlines@\texttt{readlines}}% \index{функция!writelines@\texttt{writelines}}% \begin{LongCodePar} #include #include #define MAXLINES 5000 /* максимальное число строк */ char *lineptr[MAXLINES]; /* указатели на строки */ int readlines(char *lineptr[], int nlines); void writelines(char *lineptr[], int nlines); void qsort(char *lineptr[], int left, int right); /* сортировка строк */ main() { int nlines; /* количество прочитанных строк */ if ((nlines = readlines(lineptr, MAXLINES)) >= 0) { qsort(lineptr, 0, nlines-1); writelines(lineptr, nlines); return 0; } else { printf("ошибка: слишком много строк\n"); return 1; } } #define MAXLEN 1000 /* максимальная длина строки */ int getline(char *, int); char *alloc(int); /* readlines: чтение строк */ int readlines(char *lineptr[], int maxlines) { int len, nlines; char *p, line[MAXLEN]; nlines = 0; while ((len = getline(line, MAXLEN)) > 0) if (nlines >= maxlines || (p = alloc(len)) == NULL) return -1; else { line[len-1] = '\0'; /* убираем литеру \n */ strcpy(p, line); lineptr[nlines++] = p; } return nlines; } /* writelines: печать строк */ void writelines(char *lineptr[], int nlines) { int i; for (i = 0; i < nlines; i++) printf("%s\n", lineptr[i]); } \end{LongCodePar} \noindent Функция \verb|getline| взята из разд.~\ref{sec:char_arrays}. Основное новшество здесь -- декларация \verb|lineptr|: \begin{ShortCodePar} char *lineptr[MAXLINES]; \end{ShortCodePar} \noindent в которой сообщается, что \verb|lineptr| есть массив из \verb|MAXFILES| элементов, каждый из которых представляет собой указатель на \verb|char|. Иначе говоря, \verb|lineptr[i]| -- указатель на литеру, а \verb|*lineptr[i]| -- литера, на которую он указывает (первая литера \verb|i|-й строки текста). \index{функция!writelines@\texttt{writelines}}% Так как \verb|lineptr| -- имя массива, его можно трактовать как указатель, т.е. так же, как мы это делали в предыдущих примерах, и \verb|writelines| переписать следующим образом: \begin{ShortCodePar} /* writelines: печать строк */ void writelines(char *lineptr[], int nlines) { while (nlines-- > 0) printf("%s\n", *lineptr++); } \end{ShortCodePar} \noindent Вначале \verb|*lineptr| ссылается на первую строку; каждое приращение указателя приводит к тому, что \verb|*lineptr| ссылается на следующую строку, и делается это до тех пор, пока \verb|nlines| не станет нулём. Теперь, когда мы разобрались с вводом и выводом, можно приступить к сортировке. \index{быстрая сортировка}% \index{функция!qsort@\texttt{qsort}}% Быструю сортировку, описанную в гл.~\ref{chapt:functions_and_program_structure}, надо несколько модифицировать: нужно изменить декларации, а операцию сравнения заменить обращением к \verb|strcmp|. Алгоритм остался тем же, и это даёт нам определённую уверенность в его правильности. \begin{LongCodePar} /* qsort: сортирует v[left]...v[right] по возрастанию */ void qsort(char *v[], int left, int right) { int i, last; void swap(char *v[], int i, int j); if (left >= right) /* ничего не делается, если */ return; /* в массиве менее двух элементов */ swap(v, left, (left + right)/2); last = left; for (i = left+1; i <= right; i++) if (strcmp(v[i], v[left]) < 0) swap(v, ++last, i); swap(v, left, last); qsort(v, left, last-1); qsort(v, last+1, right); } \end{LongCodePar} \noindent% \index{функция!swap@\texttt{swap}}% Небольшие поправки требуются и в программе перестановки. \begin{LongCodePar} /* swap: переставить v[i] и v[j] между собой */ void swap(char *v[], int i, int j) { char *temp; temp = v[i]; v[i] = v[j]; v[j] = temp; } \end{LongCodePar} \noindent Так как каждый элемент массива \verb|v| (т.е. \verb|lineptr|) является указателем на литеру, \verb|temp| должен иметь тот же тип, что и \verb|v| -- тогда можно будет осуществлять пересылки между \verb|temp| и элементами \verb|v|. \paragraph{Упражнение 5.7.} Напишите новую версию \verb|readlines|, которая запоминала бы строки в массиве, определённом в \verb|main|, а не запрашивала память посредством программы \verb|alloc|. Насколько быстрее эта программа? \section{Многомерные массивы} \index{массив!многомерный}% \index{массив!двумерный}% \index{декларация!массива}% \index{инициализация!двухмерных массивов}% В Си имеется возможность задавать прямоугольные многомерные массивы, правда, на практике по сравнению с массивами указателей они используются значительно реже. В этом разделе мы продемонстрируем некоторые их свойства. \index{преобразование!даты}% Рассмотрим задачу перевода даты <<день-месяц>> в <<день года>> и обратно. Например, \index{високосный год, вычисление}% 1 марта -- это 60-й день невисокосного или 61-й день високосного года. Определим две функции для этих преобразований: \index{функция!day{\_}of{\_}year@\texttt{day{\_}of{\_}year}}% функция \verb|day_of_year| будет преобразовывать месяц-день в день года‚ а \index{функция!month{\_}day@\texttt{month{\_}day}}% \verb|month_day| -- день года в месяц-день. Поскольку последняя функция вычисляет два значения, аргументы месяц и день будут указателями. Так, \begin{ShortCodePar} month_day(1988, 60, &m, &d) \end{ShortCodePar} \noindent установит в \verb|m| число $2$, а в \verb|d| -- значение $29$ (29 февраля). Нашим функциям нужна одна и та же информация, а именно таблица, содержащая числа дней каждого месяца. Так как для високосного и невисокосного годов эти таблицы будут различаться, проще иметь две отдельные строки в двумерном массиве, чем во время вычислений отслеживать особый случай с февралём. Массив и функции, выполняющие преобразования, имеют следующий вид: \begin{LongCodePar} static char daytab[2][13] = { {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} }; /* day_of_year: определяет день года по месяцу и дню */ int day_of_year(int year, int month, int day) { int i, leap; leap = year%4 == 0 && year%100 != 0 || year%400 == 0; for (i = 1; i < month; i++) day += daytab[leap][i]; return day; } /* month_day: определяет месяц и день по дню года */ void month_day(int year, int yearday, int *pmonth, int *pday) { int i, leap; leap = year%4 == 0 && year%100 != 0 || year%400 == 0; for (i = 1; yearday > daytab[leap][i]; i++) yearday -= daytab[leap][i]; *pmonth = i; *pday = yearday; } \end{LongCodePar} %% %% исправлена опечатка %% в оригинале "day_year" %% \noindent Напоминаем, что арифметическое значение логического выражения (например, выражения, с помощью которого вычислялось \verb|leap|) равно нулю (ложь) или единице (истина), так что мы можем использовать его как индекс в массиве \verb|daytab|. Массив \verb|daytab| должен быть внешним по отношению к обеим функциям \verb|day_of_year| и \verb|month_day|, так как он нужен и той и другой. Мы сделали его типа \verb|char|, чтобы проиллюстрировать законность применения типа \verb|char| для малых целых. Массив \verb|daytab| -- это первый массив из числа двумерных, с которыми мы ещё не имели дела. Строго говоря, в Си двумерный массив рассматривается как одномерный массив, каждый элемент которого -- также массив. Поэтому индексирование изображается как \begin{ShortCodePar} daytab[i][j] /* [строка][столбец] */ \end{ShortCodePar} \noindent а не как \begin{ShortCodePar} daytab[i,j] /* НЕВЕРНО */ \end{ShortCodePar} \noindent Особенность двумерного массива в Си заключается лишь в форме записи, в остальном его можно трактовать почти так же, как в других языках. \index{массив!порядок элементов в памяти}% Элементы запоминаются строками, следовательно, при переборе их в том порядке, как они расположены в памяти, чаще будет изменяться самый правый индекс. Массив инициализируется списком начальных значений, заключённым в фигурные скобки; каждая строка двумерного массива инициализируется соответствующим подсписком. Нулевой столбец добавлен в начало \verb|daytab| лишь для того, чтобы индексы, которыми мы будем пользоваться, совпадали с естественными номерами месяцев от 1 до 12. Экономить пару ячеек памяти здесь нет никакого смысла, а программа, в которой уже не надо корректировать индекс, выглядит более ясной. \index{массив!имя в роли аргумента}% \index{массив!декларация}% \index{массив!а не указатель}% \index{указатель!а не массив}% Если двумерный массив передаётся функции в качестве аргумента, то декларация соответствующего ему параметра должна содержать количество столбцов; количество строк в данном случае несущественно, поскольку, как и прежде, функции будет передана ссылка на массив строк, каждая из которых есть массив из 13 значений типа \verb|char|. В нашем частном случае имеем указатель на объекты, являющиеся массивами из 13 значений типа \verb|char|. Таким образом, если массив \verb|daytab| передаётся некоторой функции \verb|f|, то эту функцию можно было бы определить следующим образом: \begin{ShortCodePar} f(char daytab[2][13]) { ... } \end{ShortCodePar} \noindent Вместо этого можно записать \begin{ShortCodePar} f(char daytab[][13]) { ... } \end{ShortCodePar} \noindent поскольку число строк здесь не имеет значения, или \begin{ShortCodePar} f(char (*daytab)[13]) { ... } \end{ShortCodePar} \noindent последняя запись декларирует, что параметр есть указатель на массив из 13 значений типа \verb|char|. Скобки здесь необходимы, так как квадратные скобки \verb|[]| имеют более высокий приоритет, чем \verb|*|. Без скобок декларация \begin{ShortCodePar} char *daytab[13] \end{ShortCodePar} \noindent определяет массив из 13 указателей на \verb|char|. \index{по умолчанию!размер массива}% В более общем случае только первое измерение (соответствующее первому индексу) можно не задавать, все другие специфицировать необходимо. В разд.~\ref{sec:complex_declarations} мы продолжим рассмотрение сложных деклараций. \paragraph{Упражнение 5.8.} В функциях \verb|day_of_year| и \verb|month_day| нет никаких проверок правильности вводимых дат. Устраните этот недостаток. \section{Инициализация массивов указателей} \index{инициализация!массива}% \index{функция!month{\_}name@\texttt{month{\_}name}}% Напишем функцию \verb|month_name(n)|, которая возвращает ссылку на стринг литер, содержащий название \verb|n|-го месяца. Эта функция идеальна для демонстрации использования статического массива. Функция \verb|month_name| имеет в своём личном распоряжении массив стрингов, на один из которых она и возвращает ссылку. Ниже покажем, как инициализируется этот массив имён. Синтаксис задания начальных значений аналогичен синтаксису предыдущих инициализаций: \begin{LongCodePar} /* month_name: возвращает имя n-го месяца */ char *month_name(int n) { static char *name[] = { "Неверный месяц", "Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь" }; return (n < 1 || n > 12) ? name[0] : name [n]; } \end{LongCodePar} \noindent Декларация, определяющая \verb|name| как массив указателей на литеры, такая же, как и декларация \verb|lineptr| в программе сортировки. Инициализатором служит список стрингов, каждому из которых соответствует определённое место в массиве. Литеры \verb|i|-го стринга где-то размещены, и указатель на них запоминается в \verb|name[i]|. \index{массив!размер по умолчанию}% Так как размер массива \verb|name| не специфицирован, компилятор вычислит его по количеству заданных начальных значений. \section{Указатели вместо многомерных массивов} \index{массив!двумерный}% \index{массив!а не указатель}% \index{указатель!а не массив}% Начинающие программировать на Си иногда не понимают, в чём разница между двумерным массивом и массивом указателей типа \verb|name| из приведённого примера. Для двух следующих определений: \begin{ShortCodePar} int a[10][20]; int *b[10]; \end{ShortCodePar} \noindent записи \verb|a[3][4]| и \verb|b[3][4]| будут синтаксически правильными ссылками на некоторое значение типа \verb|int|. Однако только \verb|a| является истинно двумерным массивом: для двухсот элементов типа \verb|int| будет выделена память, а вычисление смещения элемента \verb|a[|\textit{\Verb|строка|}\verb|,|\textit{\Verb|столбец|}\verb|]| от начала массива будет вестись по формуле $20\times\text{\textit{строка}}+\text{\textit{столбец}}$, учитывающей его прямоугольную природу. Для \verb|b| же определяются только 10 указателей‚ причём без инициализации. Инициализация должна задаваться явно -- либо статически, либо в процессе счёта. Предположим, что каждый элемент \verb|b| ссылается на двадцатиэлементный массив, в результате где-то будут выделены пространство, в котором разместятся 200 значений типа \verb|int|, и ещё 10 ячеек для указателей. Важное преимущество массива указателей в том, что строки такого массива могут иметь разные длины. Таким образом, каждый элемент \verb|b| не обязательно ссылается на двадцатиэлементный вектор; один может ссылаться на два элемента, другой -- на пятьдесят, а некоторые и вовсе могут ни на что не ссылаться. Наши рассуждения здесь велись в отношении целых значений, однако чаще массивы указателей используются для работы со стрингами литер, различающимися по длине, как это было в функции \verb|month_name|. Сравните определение массива указателей и соответствующий ему рисунок: \begin{ShortCodePar} char *name[] = {"Неправильный месяц", "Янв", "Февр", "Март"}; \end{ShortCodePar} \begin{figure}[H] \center{\includegraphics[width=0.5617978\linewidth]{chapt5_sec9_img0.eps}} \end{figure} % % исправлена опечатка: % в оригинале все терминаторы написаны как \o % \noindent с определением и рисунком для двумерного массива: \begin{ShortCodePar} char aname[][15] = {"Неправ. месяц", "Янв", "Февр", "Март"}; \end{ShortCodePar} \begin{figure}[H] \center{\includegraphics[width=0.9831461\linewidth]{chapt5_sec9_img1.eps}} \end{figure} % % исправлена опечатка: % в оригинале все терминаторы написаны как \o % \paragraph{Упражнение 5.9.} Перепишите программы \verb|day_of_year| и \verb|month_of_year|‚ используя вместо индексов указатели. \section{Аргументы в командной строке} \index{аргументы командной строки|(}% \index{командная строка аргументы|(}% В операционной среде, обеспечивающей поддержку Си, имеется возможность передать аргументы или параметры запускаемой программе при помощи командной строки. В момент вызова \verb|main| получает два аргумента. \index{argc@\texttt{argc}}% В первом, обычно называемом \verb|argc| (сокращение от argument count), стоит количество аргументов, задаваемых в командной строке. \index{argv@\texttt{argv}|(}% Второй, \verb|argv| (от argument vector), является указателем на массив литерных стрингов, содержащих сами аргументы. Для работы с этими стрингами обычно используются указатели нескольких уровней. \index{программа!echo@\texttt{echo}}% Простейший пример -- программа \verb|echo| (<<эхо>>), которая печатает аргументы своей командной строки в одной строчке, отделяя их друг от друга пробелами. Так, команда \begin{ShortCodePar} echo Здравствуй, мир! \end{ShortCodePar} \noindent напечатает \begin{ShortCodePar} Здравствуй, мир! \end{ShortCodePar} \noindent По соглашению \verb|argv[0]| есть имя вызываемой программы, так что значение \verb|argc| никогда не бывает меньше $1$. Если \verb|argc| равен $1$, то в командной строке после имени программы никаких аргументов нет. В нашем примере \verb|argc| равен $3$, и соответственно \verb|argv[0]|‚ \verb|argv[1]| и \verb|argv[2]| есть стринги \verb|"echo"|, \verb|"Здравствуй,"| и \verb|"мир!"|. Первый необязательный аргумент -- это \verb|argv[1]|‚ последний -- \verb|argv[argc-1]|. Кроме того, стандарт требует, чтобы \verb|argv[argc]| всегда был пустым указателем. \begin{figure}[H] \center{\includegraphics[width=0.5786517\linewidth]{chapt5_sec10_img0.eps}} \end{figure} \noindent% \index{программа!echo@\texttt{echo}}% Первая версия программы \verb|echo| трактует \verb|argv| как массив литерных указателей. \begin{LongCodePar} #include /* эхо аргументов командной строки; версия 1 */ main(int argc, char *argv[]) { int i; for (i = 1; i < argc; i++) printf("%s%s", argv[i], (i < argc-1) ? " " : ""); printf("\n"); return 0; } \end{LongCodePar} \noindent% \index{программа!echo@\texttt{echo}}% Так как \verb|argv| есть указатель на массив указателей, мы можем работать с ним как с указателем, а не как с индексируемым массивом. Следующая программа основана на продвижении \verb|argv|, он продвигается так, что его значение в каждый отдельный момент ссылается на очередной указатель на \verb|char|; перебор указателей заканчивается, когда исчерпан \verb|argc|. \begin{LongCodePar} #include /* эхо аргументов командной строки; версия 2 */ main(int argc, char *argv[]) { while (--argc > 0) printf("%s%s", *++argv, (argc > 1) ? " " : ""); printf("\n"); return 0; } \end{LongCodePar} \noindent Аргумент \verb|argv| -- указатель на начало массива аргументных стрингов. Использование в \verb|++argv| префиксного оператора \verb|++| приведёт к тому, что первым будет напечатан \verb|argv[1]|, а не \verb|argv[0]|. Каждое очередное продвижение указателя даёт нам следующий аргумент, на который ссылается \verb|*argv|. В это же время значение \verb|argc| уменьшается на $1$, и, когда оно станет нулём, все аргументы будут напечатаны. Инструкцию \verb|printf| можно было бы написать и так: \begin{ShortCodePar} printf((argc > 1) ? "%s " : "%s", *++argv); \end{ShortCodePar} \noindent Как видим, формат в \verb|printf| может быть выражением. \index{программа!поиска!по образцу}% В качестве второго примера возьмём программу поиска образца, рассмотренную в разд.~\ref{sec:functions_basics}, и несколько усовершенствуем её. Если вы помните, образец для поиска мы <<вмонтировали>> глубоко в программу, а это, очевидно, не лучшее решение. Построим нашу программу по аналогии с \verb|grep| из UNIXа‚ т.е. так, чтобы образец для поиска задавался первым \index{аргументы командной строки}% аргументом в командной строке. \begin{LongCodePar} #include #include #define MAXLINE 1000 int getline(char *line, int max); /* find: печать строк с образцом заданным 1-м аргументом */ main(int argc, char *argv[]) { char line[MAXLINE]; int found = 0; if (argc != 2) printf("Используйте в find образец\n"); else while (getline(line, MAXLINE) > 0) if (strstr(line, argv[1]) != NULL) { printf("%s", line); found++; } return found; } \end{LongCodePar} \noindent Стандартная функция \verb|strstr(s,t)| выдаёт указатель на первый найденный стринг \verb|t| в стринге \verb|s| или \verb|NULL|, если такого в \verb|s| не оказалось. Её описание хранится в головном файле \verb||. Эту модель можно развивать и дальше, чтобы проиллюстрировать другие конструкции с указателями. Предположим, что мы вводим ещё два необязательных аргумента. Один из них предписывает печатать все строки, \emph{кроме} тех, в которых встречается образец; второй -- перед каждой выводимой строкой печатать её порядковый номер. По общему соглашению для Си-программ в системе UNIX знак минус перед аргументом может иногда играть роль необязательного признака или дополнительного параметра. Так, \verb|-x| служит признаком слова <<кроме>>, которое изменяет задание на противоположное, а \verb|-n| указывает на потребность в нумерации строк. Тогда, например, команда \begin{ShortCodeParWithCC}{\\\{\}} find -x -n \textit{образец} \end{ShortCodeParWithCC} \noindent напечатает все строки, в которых не найден указанный образец, и, кроме того, перед каждой строкой укажет её номер. Необязательные аргументы разрешается располагать в любом порядке, при этом лучше, чтобы остальная часть программы не зависела от числа представленных аргументов. Кроме того, пользователю было бы удобно, если бы он мог комбинировать необязательные аргументы, например, так: \begin{ShortCodeParWithCC}{\\\{\}} find -nx \textit{образец} \end{ShortCodeParWithCC} \noindent А теперь запишем нашу программу. \begin{LongCodePar} #include #include #define MAXLINE 1000 int getline(char *line, int max); /* find: печать строк по образцу из 1-го аргумента */ main(int argc, char *argv[]) { char line[MAXLINE]; long lineno = 0; int c, except = 0, number = 0, found = 0; while (--argc > 0 && (*++argv)[0] == '-') while (c = *++argv[0]) switch (c) { case 'x': except = 1; break; case 'n': number = 1; break; default: printf("find: неверный парам. %c\n", c); argc = 0; found = -1; break; } if (argc != 1) printf("Используйте: find -x -n образец\n"); else while (getline(line, MAXLINE) > 0) { lineno++; if ((strstr(line, *argv) != NULL) != except) { if (number) printf("%ld:", lineno); printf("%s", line); found++; } } return found; } \end{LongCodePar} % % исправлена ошибка наборщика с отступами в оригинале % \index{указатели!арифметика с}% Перед получением очередного аргумента \verb|argc| уменьшается на $1$, а \verb|argv| <<продвигается>> на следующий аргумент. После завершения цикла при отсутствии ошибок \verb|argc| содержит количество ещё не обработанных аргументов, а \verb|argv| указывает на первый из них. Таким образом, \verb|argc| должен быть равен $1$, а \verb|*argv| указывать на образец. Заметим, что \verb|*++argv| является указателем на аргумент-стринг, а \verb|(*++argv)[0]| -- его первой литерой, на которую можно сослаться и другим способом: \verb|**++argv|. Поскольку оператор индексации \verb|[]| имеет более высокий приоритет, чем \verb|*| и \verb|++|, круглые скобки здесь обязательны, без них запись трактовалась бы так же, как \verb|*++(argv[0])|. Именно эту запись мы применим во внутреннем цикле, где просматриваются литеры конкретного аргумента. Во внутреннем цикле выражение \verb|*++argv[0]| продвигает указатель \verb|argv[0]|. Потребность в более сложных указательных выражениях возникает не так уж часто. Но если такое случится, то, разбивая процесс вычисления указателя на два или три шага, вы облегчите восприятие этого выражения \index{argv@\texttt{argv}|)}% \index{командная строка аргументы|)}% \index{аргументы командной строки|)}% \paragraph{Упражнение 5.10.} Напишите программу \verb|expr|, интерпретирующую обратную польскую запись выражения, задаваемую командной строкой, в которой каждый оператор и операнд представлен отдельным аргументом. Например, \begin{ShortCodePar} expr 2 3 4 + * \end{ShortCodePar} \noindent вычисляется так же, как выражение $2\times(3+4)$. \paragraph{Упражнение 5.11.} Усовершенствуйте программы \verb|entab| и \verb|detab|~(см.~упражнения~\hyperref[parag:exc1.20]{1.20}~и~\hyperref[parag:exc1.21]{1.21}) таким образом, чтобы через аргументы можно было задавать список <<стопов>> табуляции. \paragraph{Упражнение 5.12.} Расширьте возможности \verb|entab| и \verb|detab| таким образом, чтобы при обращении вида \begin{ShortCodeParWithCC}{\\\{\}} entab \textit{-m} \textit{+n} \end{ShortCodeParWithCC} \noindent <<стопы>> табуляции начинались $m$-й позиции и выполнялись через каждые $n$ позиций. Разработайте удобный для пользователя вариант поведения программы по умолчанию (когда нет никаких аргументов). \paragraph{Упражнение 5.13.} Напишите программу \verb|tail|, печатающую $n$ последних введённых строк. По умолчанию значение $n$ равно $10$, но при желании $n$ можно задать с помощью аргумента. Обращение вида \begin{ShortCodeParWithCC}{\\\{\}} tail -\textit{n} \end{ShortCodeParWithCC} \noindent печатает $n$ последних строк. Программа должна вести себя осмысленно при любых входных данных и любом значении $n$. Напишите программу так, чтобы наилучшим образом использовать память; запоминание строк организуйте, как в программе сортировки, описанной в разд.~\ref{sec:arrays_of_pointers}, а не на основе двумерного массива с фиксированным размером строки. \section{Указатели на функции} \label{sec:function_pointers} \index{указатель!на функцию}% \index{функция!указатель на}% В Си сама функция не является переменной, но можно определить указатель на функцию и работать с ним, как с обычной переменной: присваивать, размещать в массиве, передавать в качестве параметра функции, возвращать как результат из функции и т.д. \index{программа!сортировки}% \index{сортировка!текстовых строк}% Для иллюстрации этих возможностей воспользуемся программой сортировки, которая уже встречалась в настоящей главе. Изменим её так, чтобы при задании необязательного аргумента \verb|-n| вводимые строки упорядочивались по их числовому значению, а не в лексикографическом порядке. Сортировка, как правило, распадается на три части: на сравнение, определяющее упорядоченность пары объектов; перестановку, меняющую порядок пары объектов на обратный, и сортирующий алгоритм, который осуществляет сравнения и перестановки до тех пор, пока все объекты не будут упорядочены. Алгоритм сортировки не зависит от операций сравнения и перестановки, так что, передавая ему различные функции сравнения и перестановки в качестве параметров, его можно настроить на различные критерии сортировки. \index{лексикографическая сортировка}% \index{сортировка!лексикографическая}% Лексикографическое сравнение двух строк выполняется функцией \verb|strcmp| (мы уже использовали эту функцию в ранее рассмотренной программе сортировки); нам также потребуется программа \verb|numcmp|, сравнивающая две строки как числовые значения и возвращающая результат сравнения в том же виде, в каком его выдаёт \verb|strcmp|. Эти функции описываются перед \verb|main|, а указатель на одну из них передаётся функции \verb|qsort|. Чтобы сосредоточиться на главном, мы упростили себе задачу, отказавшись от анализа возможных ошибок при задании аргументов. \begin{LongCodePar} #include #include #define MAXLINES 5000 /* максимальное число строк */ char *lineptr[MAXLINES]; /* указатели на строки текста */ int readlines(char *lineptr[], int nlines); void writelines(char *lineptr[], int nlines); void qsort(void *lineptr[], int left, int right, int (*comp)(void *, void *)); int numcmp(char *, char *); /* сортировка строк */ main(int argc, char *argv[]) { int nlines; /* количество прочитанных строк */ int numeric = 0; /* 1, если сорт. по числ. знач. */ if (argc > 1 && strcmp(argv[1], "-n") == 0) numeric = 1; if ((nlines = readlines(lineptr, MAXLINES)) >= 0) { qsort((void **) lineptr, 0, nlines-1, (int (*)(void*,void*))(numeric ? numcmp : strcmp)); writelines(lineptr, nlines); return 0; } else { printf("введено слишком много строк\n"); return 1; } } \end{LongCodePar} \noindent В обращениях к функциям \verb|qsort|, \verb|strcmp| и \verb|numcmp| их имена трактуются как адреса этих функций. Поэтому оператор \verb|&| перед ними не нужен, как он был не нужен и перед именем массива. \index{функция!qsort@\texttt{qsort}}% Мы написали \verb|qsort| так, чтобы она могла обрабатывать данные любого типа, а не только стринги литер. Как видно из прототипа, функция \verb|qsort| в качестве своих аргументов ожидает массив ссылок, два целых значения и функцию с двумя аргументами-указателями. \index{указатель!void *@\texttt{void *}|(}% \index{void *, указатель@\texttt{void *}, указатель|(}% В качестве указателей-аргументов заданы указатели обобщённого типа \verb|void *|. Любой указатель можно привести к типу \verb|void *| и обратно без потери информации. Поэтому мы можем обратиться к \verb|qsort|, предварительно преобразовав аргументы в \verb|void *|. Внутри функции сравнения её аргументы будут приведены к нужному ей типу. На самом деле эти преобразования никакого влияния на представления аргументов не оказывают, они лишь обеспечивают согласованность типов для компилятора. \begin{LongCodePar} /* qsort: сортирует v[left]...v[right] по возрастанию */ void qsort(void *v[], int left, int right, int (*comp)(void *, void *)) { int i, last; void swap(void *v[], int, int); if (left >= right) /* ничего не делается, если */ return; /* в массиве менее двух элементов */ swap(v, left, (left + right)/2); last = left; for (i = left+1; i <= right; i++) if ((*comp)(v[i], v[left]) < 0) swap(v, ++last, i); swap(v, left, last); qsort(v, left, last-1, comp); qsort(v, last+1, right, comp); } \end{LongCodePar} \noindent% \index{прототип функции}% \index{указатель!void *@\texttt{void *}}% \index{функция!прототип}% \index{void *, указатель@\texttt{void *}, указатель}% Повнимательней приглядимся к декларациям. Четвёртый параметр функции \verb|qsort|: \begin{ShortCodePar} int (*comp)(void *, void *) \end{ShortCodePar} \noindent сообщает, что \verb|comp| есть указатель на функцию, которая имеет два аргумента-указателя и выдаёт результат типа \verb|int|. Использование \verb|comp| в строке \begin{ShortCodePar} if ((*comp)(v[i], v[left]) < 0) \end{ShortCodePar} \noindent согласуется с декларацией <<\verb|comp| -- это указатель на функцию>>, и, следовательно, \verb|*comp| есть функция, а \begin{ShortCodePar} (*comp)(v[i], v[left]) \end{ShortCodePar} \noindent обращение к ней. Скобки здесь нужны, чтобы обеспечить правильную трактовку декларации; без них декларация \begin{ShortCodePar} int *comp(void *, void *) /* НЕВЕРНО */ \end{ShortCodePar} \noindent описывала бы \verb|comp|, как функцию, возвращающую ссылку на \verb|int|, а это совсем не то, что требуется. \index{void *, указатель@\texttt{void *}, указатель|)}% \index{указатель!void *@\texttt{void *}|)}% Мы уже рассматривали функцию \verb|strcmp|, сравнивающую два стринга. \index{функция!numcmp@\texttt{numcmp}}% Ниже приведена функция \verb|numcmp|, которая сравнивает два стринга, рассматривая их как числа; предварительно они переводятся в числовые значения функцией \verb|atof|. \begin{LongCodePar} #include /* numcmp: сравнивает s1 и s2 как числа */ int numcmp(char *s1, char *s2) { double v1, v2; v1 = atof(s1); v2 = atof(s2); if (v1 < v2) return -1; else if (v1 > v2) return 1; else return 0; } \end{LongCodePar} Программу сортировки можно пополнить и другими возможностями; реализовать некоторые из них предлагается в качестве упражнений. \paragraph{Упражнение 5.14.} Модифицируйте программу сортировки, чтобы она реагировала на параметр \verb|-r|, указывающий, что объекты нужно сортировать в обратном порядке, т.е. в порядке убывания. Обеспечьте, чтобы \verb|-r| работал и вместе с \verb|-n|. \paragraph{Упражнение 5.15.} Введите в программу необязательный параметр \verb|-f|‚ задание которого делало бы неразличимыми литеры нижнего и верхнего регистров (например, \verb|a| и \verb|A| должны оказаться при сравнении равными). \paragraph{Упражнение 5.16.} Предусмотрите в программе необязательный параметр \verb|-d|‚ который заставит программу при сравнении учитывать только буквы, цифры и пробелы. Организуйте программу таким образом, чтобы этот параметр мог работать вместе с параметром \verb|-f|. \paragraph{Упражнение 5.17.} Реализуйте в программе возможность работы с полями: возможность сортировки по полям внутри строк. Для каждого поля предусмотрите свой набор параметров. Предметный указатель этой книги\footnote{Здесь имеется в виду оригинал книги на английском языке. -- \textit{Примеч. пер.}} упорядочивался с параметрами: \verb|-df| для терминов и \verb|-n| для номеров страниц. \section{Сложные декларации} \label{sec:complex_declarations} Иногда Си ругают за синтаксис деклараций, особенно тех, которые содержат в себе указатели на функции. Таким синтаксис получился в результате нашей попытки сделать похожими записи объектов при их описании и использовании. В простых случаях этот синтаксис хорош, однако в сложных ситуациях он вызывает затруднения, поскольку декларации перенасыщены скобками и их невозможно читать слева направо. Проблему иллюстрирует различие следующих двух деклараций: \begin{ShortCodePar} int *f(); /* f: фу-ция, возвращающая ук-ль на int */ int (*pf)(); /* pf: ук-ль на ф-цию, возвращающую int */ \end{ShortCodePar} \noindent Приоритет префиксного оператора \verb|*| ниже, чем приоритет \verb|()|, поэтому во втором случае скобки необходимы. Хотя на практике по-настоящему сложные декларации встречаются редко, всё же важно знать‚ как их понимать, а если потребуется, и как их конструировать. Укажем хороший способ: декларации можно синтезировать, двигаясь небольшими шагами с помощью \verb|typedef|; этот способ рассмотрен в разд.~\ref{sec:typedef}. В настоящем разделе на примере двух программ, осуществляющих преобразования правильных Си-деклараций в соответствующие им словесные описания и обратно, мы демонстрируем иной способ конструирования деклараций. Словесное описание читается слева направо. \index{программа!dcl@\texttt{dcl}}% Первая программа, \verb|dcl|, -- более сложная. Она преобразует Си-декларации в словесные описания так, как показано в следующих примерах: \begin{LongCodePar} char **argv argv: указ. на указ. на char int (*daytab)[13] daytab: указ. на массив[13] из int void *comp() comp: функц. возвр. указ. на void void (*comp)() comp: указ. на функц. возвр. void char (*(*x())[])() x: функц. возвр. указ. на массив[] из указ. на функц. возвр. char char (*(*x[3])())[5] x: массив[3] из указ. на функц. возвр. указ. на массив[5] из char \end{LongCodePar} Функция \verb|dcl| в своей работе использует грамматику, специфицирующую декларатор. Эта грамматика строго изложена в разд.~\ref{apx:subsec:declarators} приложения~\ref{apx:ref_manual}, а в упрощённом виде записывается так: \begin{ShortCodeParWithCC}{\\\{\}} \textit{dcl:} \textit{optional} *\textit{'s} \textit{direct-dcl} \textit{direct-dcl:} \textit{name} (\textit{dcl}) \textit{direct-dcl}() \textit{direct-dcl}[\textit{optional size}] \end{ShortCodeParWithCC} \noindent Говоря простым языком, \textit{dcl} есть \textit{direct-dcl}, перед которым может стоять \verb|*|\textit{'s} (т.е. одна или несколько звёздочек), где \textit{direct-dcl} есть \textit{name} (имя), или \textit{dcl} в скобках, или \textit{direct-dcl} с последующей парой скобок, или \textit{direct-dcl} с последующей парой квадратных скобок, внутри которых может быть помещён размер (\textit{size}). Эту грамматику можно использовать для грамматического разбора деклараций. Рассмотрим, например, такой декларатор: \begin{ShortCodePar} (*pfa[])() \end{ShortCodePar} \noindent Имя \verb|pfa| будет классифицировано как \textit{name} и, следовательно, как \textit{direct-dcl}. Затем \verb|pfa[]| будет распознано как \textit{direct-dcl}, а \verb|*pfa[]| -- как \textit{dcl} и, следовательно, \verb|(*pfa[])| есть \textit{direct-dcl}. Далее \verb|(*pfa[])()| есть \textit{direct-dcl} и, таким образом, \textit{dcl}. Этот грамматический разбор можно проиллюстрировать \index{дерево!разбора}% деревом разбора (здесь \textit{direct-dcl} обозначено более коротко, а именно \textit{dir-dcl}): \begin{figure}[H] \center{\includegraphics[width=0.7627119\linewidth]{chapt5_sec12_img0.eps}} \end{figure} % % TODO: проверить правильность синтаксического дерева % \index{грамматический разбор методом рекурсивного спуска}% \index{функция!dcl@\texttt{dcl}}% \index{функция!dirdcl@\texttt{dirdcl}}% Сердцевиной программы обработки декларатора является пара функций \verb|dcl| и \verb|dirdcl|‚ осуществляющих грамматический разбор декларации согласно приведённой грамматике. Поскольку грамматика определена рекурсивно, эти функции обращаются друг к другу рекурсивно, по мере распознавания отдельных частей декларации. \index{рекурсивный спуск в грамматическом разборе}% Метод, применённый в обсуждаемой программе для грамматического разбора, называется рекурсивным спуском. \begin{LongCodePar} /* dcl: разбор декларатора */ void dcl(void) { int ns; for (ns = 0; gettoken() == '*'; ) /* подсчёт звёздочек */ ns++; dirdcl(); while (ns-- > 0) strcat(out, " указ. на"); } /* dirdcl: разбор непосредственного декларатора */ void dirdcl(void) { int type; if (tokentype == '(') { /* ( dcl ) */ dcl(); if (tokentype != ')') printf("ошибка: пропущена )\n"); } else if (tokentype == NAME) /* имя переменной */ strcpy(name, token); else printf("ошибка: должно быть name или (dcl)\n"); while ((type=gettoken()) == PARENS || type == BRACKETS) if (type == PARENS) strcat(out, " функц. возвр."); else { strcat(out, " массив"); strcat(out, token); strcat(out, " из"); } } \end{LongCodePar} Приведённые программы служат только иллюстративным целям и не вполне надёжны. Что касается \verb|dcl|, то её возможности существенно ограничены. Она может работать только с простыми типами вроде \verb|char| и \verb|int| и не справляется с типами аргументов в функциях и с квалификаторами подобными \verb|const|. Лишние пробелы для неё опасны. Она не предпринимает никаких мер по выходу из ошибочной ситуации, и поэтому неправильные декларации также противопоказаны ей. Устранение этих недостатков мы оставляем до упражнений. Ниже приведены глобальные переменные и главная программа. \begin{LongCodePar} #include #include #include #define MAXTOKEN 100 enum { NAME, PARENS, BRACKETS }; void dcl(void); void dirdcl(void); int gettoken(void); int tokentype; /* тип последней лексемы */ char token[MAXTOKEN]; /* текст последней лексемы */ char name[MAXTOKEN]; /* имя */ char datatype[MAXTOKEN]; /* тип = char, int и т.д. */ char out[1000]; /* выдаваемый текст */ main() /* преобразование декларации в словесное описание */ { while (gettoken() != EOF) { /* 1-я лексема в строке */ strcpy(datatype, token); /* это тип данных */ out[0] = '\0'; dcl(); /* разбор остальной части строки */ if (tokentype != '\n') printf("синтаксическая ошибка\n"); printf("%s: %s %s\n", name, out, datatype); } return 0; } \end{LongCodePar} \index{функция!gettoken@\texttt{gettoken}}% Функция \verb|gettoken| пропускает пробелы и табуляции и затем получает следующую лексему из ввода; <<лексема>> -- это имя, или пара круглых скобок, или пара квадратных скобок (быть может, с помещённым в них числом), или любая другая единичная литера. \begin{LongCodePar} int gettoken(void) /* возвращает следующую лексему */ { int c, getch(void); void ungetch(int); char *p = token; while ((c = getch()) == ' ' || c == '\t') ; if (c == '(') { if ((c = getch()) == ')') { strcpy(token, "()"); return tokentype = PARENS; } else { ungetch(c); return tokentype = '('; } } else if (c == '[') { for (*p++ = c; (*p++ = getch()) != ']'; ) ; *p = '\0'; return tokentype = BRACKETS; } else if (isalpha(c)) { for (*p++ = c; isalnum(c = getch()); ) *p++ = c; *p = '\0'; ungetch(c); return tokentype = NAME; } else return tokentype = c; } \end{LongCodePar} \noindent Функции \verb|getch| и \verb|ungetch| были рассмотрены в гл.~\ref{chapt:functions_and_program_structure}. Обратное преобразование реализуется легче, особенно если не придавать значения тому, что будут генерироваться лишние скобки. \index{программа!undcl@\texttt{undcl}}% Программа \verb|undcl| превращает фразу типа <<\verb|x| есть функция, возвращающая указатель на массив указателей на функции, возвращающие \verb|char|>>, которую мы будем представлять в виде \begin{ShortCodePar} x () * [] () char \end{ShortCodePar} \noindent в декларацию \begin{ShortCodePar} char (*(*x())[])() \end{ShortCodePar} \noindent Сокращённая запись словесного описания позволяет воспользоваться функцией \verb|gettoken|. Функция \verb|undcl| использует те же самые внешние переменные, что и \verb|dcl|. \begin{LongCodePar} /* undcl: преобразует словесное описание в декларацию */ main() { int type; char temp[MAXTOKEN]; while (gettoken() != EOF) { strcpy(out, token); while ((type = gettoken()) != '\n') if (type == PARENS || type == BRACKETS) strcat(out, token); else if (type == '*') { sprintf(temp, "(*%s)", out); strcpy(out, temp); } else if (type == NAME) { sprintf(temp, "%s %s", token, out); strcpy(out, temp); } else printf("неверный элемент %s в фразе\n",token); printf("%s\n", out); } return 0; } \end{LongCodePar} % % исправлена опечатка % отсутствовали ; % % в целях выравнивания % строка "printf("неверный элемент %s в фразе\n", token);" % заменена на "printf("неверный элемент %s в фразе\n",token);", % что не влияет на смысл кода % \paragraph{Упражнение 5.18.} Видоизмените \verb|dcl| таким образом, чтобы она нормально обрабатывала ошибки во входной информации. \paragraph{Упражнение 5.19.} Модифицируйте \verb|undcl| так, чтобы она не генерировала лишних скобок. \paragraph{Упражнение 5.20.} Расширьте возможности \verb|dcl|: функция должна справляться с описателями типов её аргументов, квалификаторами \verb|const| и т.д.