| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208 |
- \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 <ctype.h>
- 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{файл!головной!<stdio.h>@\texttt{<stdio.h>}}%
- \index{NULL@\texttt{NULL}}%
- \index{stdio.h@\texttt{<stdio.h>}}%
- Чтобы показать, что нуль -- это специальное значение для указателя, вместо цифры
- нуль, как правило, записывают \verb|NULL| -- константу, определённую в файле
- \verb|<stdio.h>|. С этого момента и мы будем ею пользоваться.
- Проверки
- \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{файл!головной!<stddef.h>@\texttt{<stddef.h>}}%
- \index{ptrdiff{\_}t@\texttt{ptrdiff{\_}t}}%
- Тип \verb|ptrdiff_t|, достаточный для хранения разности (со знаком) двух
- указателей, определён в головном файле \verb|<stddef.h>|.
- \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{файл!головной!<string.h>@\texttt{<string.h>}}%
- Что касается функции \verb|strcpy| из стандартной библиотеки \verb|<string.h>|‚
- то она возвращает в качестве своего результата ещё и ссылку на новую копию
- стринга.
- \index{функция!strcmp@\texttt{strcmp}}%
- Вторая программа, которую мы здесь рассмотрим, это \verb|strcmp(s,t)|. Она
- сравнивает литеры стрингов \verb|s| и \verb|t| и возвращает отрицательное,
- нулевое или положительное значение, если стринг \verb|s| соответственно
- лексикографически меньше, равен или больше, чем стринг \verb|t|. Результат
- получается вычитанием первых несовпадающих литер из \verb|s| и \verb|t|.
- \begin{LongCodePar}
- /* strcmp: выдаёт <0 при s<t, 0 при s==t, >0 при 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 при s<t, 0 при s==t, >0 при 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{файл!головной!<string.h>@\texttt{<string.h>}}%
- Описания функций, упомянутых в этом разделе, а также ряда других стандартных
- функций, работающих со стрингами, содержатся в головном файле \verb|<string.h>|.
- \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 <stdio.h>
- #include <string.h>
- #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 <stdio.h>
- /* эхо аргументов командной строки; версия 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 <stdio.h>
- /* эхо аргументов командной строки; версия 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 <stdio.h>
- #include <string.h>
- #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|<string.h>|.
- Эту модель можно развивать и дальше, чтобы проиллюстрировать другие конструкции
- с указателями. Предположим, что мы вводим ещё два необязательных аргумента. Один
- из них предписывает печатать все строки, \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 <stdio.h>
- #include <string.h>
- #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 <stdio.h>
- #include <string.h>
- #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 <stdlib.h>
- /* 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 <stdio.h>
- #include <string.h>
- #include <ctype.h>
- #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| и
- т.д.
|