| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599 |
- \chapter{Структуры}
- \label{chapt:structures}
- Структура -- это одна или несколько переменных (возможно, различных типов),
- которые для удобства работы с ними сгруппированы под одним именем. (В некоторых
- языках, в частности в Паскале, структуры называются записями.) Структуры
- помогают в организации сложных данных (особенно в больших программах), поскольку
- позволяют группу связанных между собой переменных трактовать не как множество
- отдельных элементов, а как единое целое.
- Распространённый пример структуры -- строка платёжной ведомости. Она содержит
- такие сведения о служащем, как его полное имя, адрес, номер карточки социального
- страхования, зарплата и т.д. Некоторые из этих характеристик сами могут быть
- структурами: например, полное имя состоит из нескольких компонент (фамилии‚
- имени и отчества); аналогично адрес, и даже зарплата. Другой пример (более
- типичный для Си) -- из области графики: точка есть пара координат,
- прямоугольник есть пара точек и т.д.
- Главные изменения, внесённые стандартом ANSI в отношении структур, -- это
- введение для них операции присваивания. Структуры могут копироваться, над ними
- могут выполняться операции присваивания, их можно передавать функциям в
- качестве аргументов, а функции могут возвращать их в качестве результатов. В
- большинстве компиляторов уже давно реализованы эти возможности, но теперь они
- точно оговорены стандартом. Для автоматических структур и массивов теперь также
- допускается инициализация.
- \section{Основные сведения о структурах}
- Сконструируем несколько графических структур. В качестве основного объекта
- выступает точка с координатами $x$ и $y$, и пусть они имеют тип \verb|int|.
- \begin{figure}[H]
- \center{\includegraphics[width=0.3841808\linewidth]{chapt6_sec1_img0.eps}}
- \end{figure}
- \noindent%
- \index{декларация!структуры}%
- \index{структура!декларация}%
- Указанные две компоненты можно поместить в структуру, описанную, например,
- следующим образом:
- \begin{ShortCodePar}
- struct point {
- int x;
- int y;
- };
- \end{ShortCodePar}
- Описание структуры начинается с ключевого слова \verb|struct| и содержит список
- деклараций, заключённый в фигурные скобки.
- \index{структура!тег}%
- \index{тег!структуры}%
- За словом \verb|struct| может следовать имя, называемое
- \emph{тегом}\footnote{От английского слова tag -- ярлык, этикетка. --
- \textit{Примеч. пер.}} \emph{структуры} (\verb|point| в нашем случае). Тег даёт
- название структуре данного вида и далее может служить кратким обозначением той
- части декларации, которая заключена в фигурные скобки.
- \index{структура!имя члена}%
- \index{член структуры, имя}%
- Перечисленные в структуре переменные называются \emph{членами}. Имена членов и
- тегов без каких-либо коллизий могут совпадать с именами обычных переменных (т.е.
- не членов), так как они всегда различимы по контексту. Более того, одни и те же
- имена членов могут встречаться в разных структурах, хотя, если следовать
- хорошему стилю программирования, лучше одинаковые имена давать только близким по
- смыслу объектам.
- \index{декларация!структуры}%
- \index{структура!декларация}%
- Декларация структуры -- это тип. За правой фигурной скобкой, закрывающей список
- членов, могут следовать переменные точно так же, как они могут быть указаны
- после названия любого базового типа. Таким образом, запись
- \begin{ShortCodePar}
- struct { ... } x, y, z;
- \end{ShortCodePar}
- \noindent с точки зрения синтаксиса аналогична записи
- \begin{ShortCodePar}
- int x, y, z;
- \end{ShortCodePar}
- \noindent в том смысле, что каждая декларирует \verb|x|, \verb|y| и \verb|z| как
- переменные указанного типа. Обе записи приведут к тому, что где-то будет
- выделена память соответствующего размера.
- \index{декларация!структуры}%
- \index{структура!декларация}%
- Декларация структуры, не содержащей списка переменных, не резервирует памяти:
- она просто описывает шаблон, или образец структуры. Однако если структура имеет
- тег, то этим тегом далее можно пользоваться при определении структурных
- объектов. Например, с помощью заданной выше декларации структуры
- \verb|point| строка
- \begin{ShortCodePar}
- struct point pt;
- \end{ShortCodePar}
- \noindent определяет структурную переменную \verb|pt| типа \verb|struct point|.
- \index{инициализация!структуры}%
- \index{структура!инициализация}%
- \index{фигурные скобки}%
- Структурную переменную при её определении можно инициализировать, формируя
- список инициализаторов её членов в виде константных выражений:
- \begin{ShortCodePar}
- struct point maxpt = { 320, 200 };
- \end{ShortCodePar}
- \noindent Инициализировать автоматические структуры можно также присваиванием
- или обращением к функции, возвращающей результат в виде структуры
- соответствующего типа.
- \index{оператор!доступа к члену структуры!точка@\texttt{.} (точка)}%
- \index{структура!оператор доступа к её члену!\texttt{.} (точка)}%
- Доступ к отдельному члену структуры осуществляется посредством конструкции вида:
- \begin{ShortCodeParWithCC}{\\\{\}}
- \textit{имя-структуры} . \textit{член}
- \end{ShortCodeParWithCC}
- \noindent Оператор доступа к члену структуры <<\verb|.|>> соединяет имя
- структуры и имя члена. Чтобы напечатать, например, координаты точки \verb|pt|,
- годится следующее обращение к \verb|printf|:
- \begin{ShortCodePar}
- printf("%d,%d", pt.x, pt.y);
- \end{ShortCodePar}
- \noindent Другой пример: чтобы вычислить расстояние от начала координат $(0, 0)$
- до \verb|pt|, можно написать
- \begin{ShortCodePar}
- double dist, sqrt(double);
- dist = sqrt((double)pt.x * pt.x + (double)pt.y * pt.y);
- \end{ShortCodePar}
- \index{структура!вложенная}%
- Структуры могут быть вложены друг в друга. Одно из возможных представлений
- прямоугольника -- это пара точек на углах одной из его диагоналей:
- \begin{figure}[H]
- \center{\includegraphics[width=0.5056180\linewidth]{chapt6_sec1_img1.eps}}
- \end{figure}
- \begin{ShortCodePar}
- struct rect {
- struct point pt1;
- struct point pt2;
- };
- \end{ShortCodePar}
- \noindent Структура \verb|rect| содержит две структуры \verb|point|. Если мы
- декларируем \verb|screen| как
- \begin{ShortCodePar}
- struct rect screen;
- \end{ShortCodePar}
- \noindent то
- \begin{ShortCodePar}
- screen.pt1.x
- \end{ShortCodePar}
- \noindent ссылается на координату $x$ точки \verb|pt1| из \verb|screen|.
- \section{Структуры и функции}
- Единственно возможные операции над структурами -- это их копирование,
- присваивание, взятие адреса с помощью \verb|&| и осуществление доступа к её
- членам. Передача структур функциям в качестве аргументов и возврат их от
- функций в виде результата также относятся к операциям копирования и
- присваивания. Структуры нельзя сравнивать. Инициализировать структуру можно
- списком константных значений её членов; автоматическую структуру можно
- инициализировать также присваиванием.
- Чтобы лучше познакомиться со структурами, напишем несколько функций,
- манипулирующих точками и прямоугольниками. Возникает вопрос: а как передавать
- функциям названные объекты? Существует по крайней мере три подхода: передавать
- компоненты по отдельности, передавать всю структуру целиком и передавать
- указатель на структуру. Каждый подход имеет свои плюсы и минусы.
- \index{функция!makepoint@\texttt{makepoint}}%
- Первая функция, \verb|makepoint|, получает два целых значения и возвращает
- структуру \verb|point|.
- \begin{LongCodePar}
- /* makepoint: формирует точку по компонентам x и y */
- struct point makepoint(int x, int y)
- {
- struct point temp;
- temp.x = x;
- temp.y = y;
- return temp;
- }
- \end{LongCodePar}
- \noindent Заметим: никакой путаницы из-за того, что имя аргумента совпадает с
- именем члена структуры не возникает; более того, одно и то же имя подчёркивает
- родство обозначаемых им объектов.
- Теперь с помощью \verb|makepoint| можно выполнять динамическую инициализацию
- любой структуры или формировать структурные аргументы для той или иной функции:
- \begin{ShortCodePar}
- struct rect screen;
- struct point middle;
- struct point makepoint(int, int);
- screen.pt1 = makepoint(0, 0);
- screen.pt2 = makepoint(XMAX, YMAX);
- middle = makepoint((screen.pt1.x + screen.pt2.x)/2,
- (screen.pt1.y + screen.pt2.y)/2);
- \end{ShortCodePar}
- \index{функция!addpoint@\texttt{addpoint}}%
- Нам может понадобиться ряд функций, реализующих различные операции над точками.
- В качестве примера рассмотрим следующую функцию:
- \begin{ShortCodePar}
- /* addpoint: сложение двух точек */
- struct point addpoint(struct point p1, struct point p2)
- {
- p1.x += p2.x;
- p1.y += p2.y;
- return p1;
- }
- \end{ShortCodePar}
- \noindent Здесь оба аргумента и возвращаемое значение -- структуры. Мы
- увеличиваем компоненты прямо в \verb|p1| и не используем для этого временной
- переменной, чтобы подчеркнуть, что структурные параметры передаются по значению
- так же, как и любые другие.
- \index{функция!ptinrect@\texttt{ptinrect}}%
- В качестве другого примера приведём функцию \verb|ptinrect|, которая проверяет:
- находится ли точка внутри прямоугольника, относительно которого мы принимаем
- соглашение, что в него входят его левая и нижняя стороны, но не входят верхняя и
- правая.
- \begin{ShortCodePar}
- /* ptinrect: возвращает 1, если p в r, и 0 в прот. случае */
- int ptinrect(struct point p, struct rect r)
- {
- return p.x >= r.pt1.x && p.x < r.pt2.x
- && p.y >= r.pt1.y && p.y < r.pt2.y;
- }
- \end{ShortCodePar}
- \noindent Здесь предполагается, что прямоугольник представлен в стандартном
- виде, т.е. координаты точки \verb|pt1| меньше соответствующих координат точки
- \verb|pt2|.
- \index{функция!canonrect@\texttt{canonrect}}%
- Следующая функция гарантирует получение прямоугольника в
- каноническом виде.
- \begin{LongCodePar}
- #define min(a, b) ((a) < (b) ? (a) : (b))
- #define max(a, b) ((a) > (b) ? (a) : (b))
- /* canonrect: канонизация координат прямоугольника */
- struct rect canonrect(struct rect r)
- {
- struct rect temp;
- temp.pt1.x = min(r.pt1.x, r.pt2.x);
- temp.pt1.y = min(r.pt1.y, r.pt2.y);
- temp.pt2.x = max(r.pt1.x, r.pt2.x);
- temp.pt2.y = max(r.pt1.y, r.pt2.y);
- return temp;
- }
- \end{LongCodePar}
- Если функции передаётся большая структура, то, чем копировать её целиком,
- эффективнее передать указатель на неё. Указатели на структуры ничем не
- отличаются от указателей на обычные переменные. Декларация
- \begin{ShortCodePar}
- struct point *pp;
- \end{ShortCodePar}
- \noindent сообщает, что \verb|pp| есть указатель на структуру типа
- \verb|struct point|. Если \verb|pp| ссылается на структуру \verb|point|, то
- \verb|*pp| есть сама структура, а \verb|(*pp).x| и \verb|(*pp).y| -- её члены.
- Используя указатель \verb|pp|, мы могли бы написать
- \begin{ShortCodePar}
- struct point origin, *pp;
- pp = &origin;
- printf("origin: (%d,%d)\n", (*pp).x, (*pp).y);
- \end{ShortCodePar}
- \noindent%
- \index{оператор!приоритет}%
- Скобки в \verb|(*pp).x| необходимы поскольку приоритет оператора \verb|.| выше,
- чем приоритет \verb|*|. Выражение \verb|*pp.x| будет проинтерпретировано как
- \verb|*(pp.x)|‚ что неверно, поскольку \verb|pp.x| не является указателем.
- \index{оператор!доступа к члену структуры!через указатель \texttt{\textminus\textgreater}}%
- \index{структура!оператор доступа к её члену!через указатель \texttt{\textminus\textgreater}}%
- Указатели на структуры используются весьма часто, поэтому для доступа к её
- членам была придумана ещё одна, более короткая форма записи. Если \verb|p| --
- указатель на структуру, то
- \begin{ShortCodeParWithCC}{\\\{\}}
- p->\textit{член-структуры}
- \end{ShortCodeParWithCC}
- \noindent есть её отдельный член. (Оператор \verb|->| состоит из знака
- \verb|-|, за которым сразу следует знак \verb|>|.) Поэтому \verb|printf| можно
- переписать в виде
- \begin{ShortCodePar}
- printf("origin: (%d,%d)\n", pp->x, pp->y);
- \end{ShortCodePar}
- \index{оператор!приоритет}%
- \index{приоритеты операторов}%
- Оба оператора \verb|.| и \verb|->| выполняются слева направо. Таким образом, при
- наличии декларации
- \begin{ShortCodePar}
- struct rect r, *rp = r;
- \end{ShortCodePar}
- \noindent следующие четыре выражения будут эквивалентны:
- \begin{ShortCodePar}
- r.pt1.x
- rp->pt1.x
- (r.pt1).x
- (rp->pt1).x
- \end{ShortCodePar}
- \index{оператор!приоритет}%
- \index{приоритеты операторов}%
- Операторы доступа к членам структуры \verb|.| и \verb|->| вместе с операторами
- вызова функции \verb|()| и индексации массива \verb|[]| занимают самое высокое
- положение в иерархии приоритетов и выполняются раньше любых других операторов.
- Например, если задана декларация
- \begin{ShortCodePar}
- struct {
- int len;
- char *str;
- } *p;
- \end{ShortCodePar}
- \noindent то
- \begin{ShortCodePar}
- ++p->len
- \end{ShortCodePar}
- \noindent увеличит на $1$ значение члена структуры \verb|len|, а не указатель
- \verb|p|, поскольку в этом выражении как бы неявно присутствуют скобки:
- \verb|++(p->len)|. Чтобы изменить порядок выполнения операций, нужны явные
- скобки. Так, в \verb|(++p)->len|, прежде чем взять значение \verb|len|,
- программа
- %
- % в оригинале слева внизу страницы ``5. Заказ № 13''
- %
- продвинет указатель \verb|p|. В \verb|(p++)->len| указатель \verb|p| увеличится
- после того, как будет взято значение \verb|len| (в последнем случае скобки не
- обязательны).
- \index{оператор!приоритет}%
- \index{приоритеты операторов}%
- По тем же правилам \verb|*p->str| обозначает содержимое объекта, на который
- ссылается \verb|str|; \verb|*p->str++| продвинет указатель \verb|str| после
- получения значения объекта, на который он указывал (как и в выражении вида
- \verb|*s++|); \verb|(*p->str)++| увеличит значение объекта, на который ссылается
- \verb|str|; \verb|*p++->str| продвинет \verb|p| после того, как будет получено
- то, на что указывает \verb|str|.
- \section{Массивы структур}
- \index{массив!структур}%
- \index{программа!подсчёта!ключевых слов}%
- Рассмотрим программу, определяющую число вхождений каждого ключевого слова в
- текст Си-программы. Нам нужно уметь хранить ключевые слова в виде массива
- стрингов и счётчики ключевых слов в виде массива целых. Один из возможных
- вариантов -- это иметь два параллельных массива:
- \begin{ShortCodePar}
- char *keyword[NKEYS];
- int keycount[NKEYS];
- \end{ShortCodePar}
- \noindent Однако именно тот факт, что они параллельны, подсказывает нам другую
- организацию хранения -- через массив структур. Каждое ключевое слово можно
- описать парой характеристик
- \begin{ShortCodePar}
- char *word;
- int count;
- \end{ShortCodePar}
- \noindent Такие пары составляют массив. Декларация
- \begin{ShortCodePar}
- struct key {
- char *word;
- int count;
- } keytab[NKEYS];
- \end{ShortCodePar}
- \noindent описывает структуру типа \verb|key| и определяет массив \verb|keytab|,
- каждый элемент которого есть структура этого типа и которому где-то будет
- выделена память. Это же можно записать и по-другому:
- \begin{ShortCodePar}
- struct key {
- char *word;
- int count;
- };
- struct key keytab[NKEYS];
- \end{ShortCodePar}
- Так как \verb|keytab| содержит постоянный набор имён, его легче всего сделать
- внешним массивом и инициализировать один раз в момент определения.
- \index{массив!структур!инициализация}%
- \index{инициализация!массивов структур}%
- Инициализация структур аналогична ранее демонстрировавшимся инициализациям -- за
- определением следует список инициализаторов, заключённый в фигурные скобки:
- \begin{LongCodePar}
- struct key {
- char *word;
- int count;
- } keytab[] = {
- "auto", 0,
- "break", 0,
- /* ... */
- "while", 0
- };
- \end{LongCodePar}
- \noindent%
- \index{массив!структур!инициализация}%
- \index{инициализация!массивов структур}%
- Инициализаторы задаются парами, чтобы соответствовать конфигурации структуры.
- Строго говоря, пару инициализаторов для каждой отдельной структуры следовало бы
- заключить в фигурные скобки, как, например, в
- \begin{ShortCodePar}
- { "auto", 0 },
- { "break", 0 },
- ...
- \end{ShortCodePar}
- \noindent Однако, когда инициализаторы -- простые константы или цепочки литер,
- и все они имеются в наличии, во внутренних скобках нет необходимости.
- \index{массив!размер по умолчанию}%
- \index{по умолчанию!размер массива}%
- Число элементов массива \verb|keytab| будет вычислено по количеству
- инициализаторов, поскольку они представлены полностью, а внутри квадратных
- скобок \verb|[]| ничего не задано.
- Программа подсчёта ключевых слов начинается с определения \verb|keytab|.
- Программа \verb|main| читает ввод, многократно обращаясь к функции
- \verb|getword| и получая на каждом её вызове очередное слово. Каждое слово
- ищется в \verb|keytab|.
- \index{функция!binsearch@\texttt{binsearch}}%
- Для этого используется функция бинарного поиска, которую
- мы написали в гл.~\ref{chapt:control_flow}. Список ключевых слов должен быть
- упорядочен в алфавитном порядке.
- \begin{LongCodePar}
- #include <stdio.h>
- #include <ctype.h>
- #include <string.h>
- #define MAXWORD 100
- int getword(char *, int);
- int binsearch(char *, struct key *, int);
- /* подсчёт ключевых слов Си */
- main()
- {
- int n;
- char word[MAXWORD];
- while (getword(word, MAXWORD) != EOF)
- if (isalpha(word[0]))
- if ((n = binsearch(word, keytab, NKEYS)) >= 0)
- keytab[n].count++;
- for (n = 0; n < NKEYS; n++)
- if (keytab[n].count > 0)
- printf("%4d %s\n",
- keytab[n].count, keytab[n].word);
- return 0;
- }
- /* binsearch: найти слово в tab[0]...tab[n-1] */
- int binsearch(char *word, struct key tab[], int n)
- {
- int cond;
- int low, high, mid;
- low = 0;
- high = n - 1;
- while (low <= high) {
- mid = (low+high) / 2;
- if ((cond = strcmp(word, tab[mid].word)) < 0)
- high = mid - 1;
- else if (cond > 0)
- low = mid + 1;
- else
- return mid;
- }
- return -1;
- }
- \end{LongCodePar}
- %
- % в оригинале слева внизу страницы ``5*''
- %
- \noindent Чуть позже мы рассмотрим функцию \verb|getword|, а сейчас нам
- достаточно знать, что при каждом её вызове получается очередное слово, которое
- запоминается в массиве, заданном первым аргументом.
- \verb|NKEYS| -- количество ключевых слов в \verb|keytab|. Хотя мы могли бы
- подсчитать число таких слов вручную, гораздо легче и безопасней сделать это с
- помощью машины, особенно если список ключевых слов может быть изменён. Одно из
- возможных решений -- поместить в конец списка инициализаторов пустой указатель
- (\verb|NULL|) и затем перебирать в цикле элементы \verb|keytab|, пока не
- встретится концевой элемент.
- Но возможно и более простое решение. Поскольку размер массива полностью
- определён во время компиляции и равен произведению количества элементов массива
- на размер его отдельного элемента, число элементов массива можно вычислить по
- формуле
- \begin{ShortCodeParWithCC}{\\\{\}}
- \textit{размер} keytab / \textit{размер} struct key
- \end{ShortCodeParWithCC}
- \noindent%
- \index{оператор!sizeof@\texttt{sizeof}}%
- В Си имеется унарный оператор \verb|sizeof|‚ который работает во время
- компиляции. Его можно применять для вычисления размера любого объекта. Выражения
- \begin{ShortCodeParWithCC}{\\\{\}}
- sizeof \textit{объект}
- \end{ShortCodeParWithCC}
- \noindent и
- \begin{ShortCodeParWithCC}{\\\{\}}
- sizeof(\textit{имя типа})
- \end{ShortCodeParWithCC}
- \noindent выдают целые значения, равные размеру указанного объекта или типа в
- байтах.
- \index{файл!головной!<stddef.h>@\texttt{<stddef.h>}}%
- \index{size{\_}t@\texttt{size{\_}t}}%
- (Строго говоря, \verb|sizeof| выдаёт беззнаковое целое, тип которого
- \verb|size_t| определён в головном файле \verb|<stddef.h>|.) Что касается
- объекта, то это может быть переменная, массив или структура. В качестве имени
- типа может выступать имя базового типа (\verb|int|, \verb|double|, \ldots) или
- имя производного типа, например, структуры или указателя.
- В нашем случае, чтобы вычислить количество ключевых слов, размер массива надо
- поделить на размер одного элемента. Указанное вычисление используется в
- инструкции \verb|#define| для установки значения \verb|NKEYS|:
- \begin{ShortCodePar}
- #define NKEYS (sizeof keytab / sizeof(struct key))
- \end{ShortCodePar}
- \noindent Этот же результат можно получить другим способом -- поделить размер
- массива на размер какого-то его конкретного элемента:
- \begin{ShortCodePar}
- #define NKEYS (sizeof keytab / sizeof keytab[0])
- \end{ShortCodePar}
- \noindent Преимущество такого рода записей в том, что их не надо корректировать
- при изменении типа.
- \index{оператор!sizeof@\texttt{sizeof}}%
- \index{if@\texttt{{\#}if}}%
- Поскольку препроцессор не обращает внимания на имена типов, оператор
- \verb|sizeof| нельзя применять в \verb|#if|. Но в \verb|#define| выражение
- препроцессором не вычисляется, так что предложенная нами запись допустима.
- \index{функция!getword@\texttt{getword}}%
- Теперь поговорим о функции \verb|getword|. Мы написали \verb|getword| в
- несколько более общем виде, чем требуется для нашей программы, но она от этого
- не стала заметно сложнее. Функция \verb|getword| берет из входного потока
- следующее <<слово>>. Под словом понимается цепочка букв-цифр, начинающаяся с
- буквы, или отдельная непробельная литера. По концу файла функция выдаёт
- \verb|EOF|, в остальных случаях её значением является код первой литеры слова
- или код отдельной литеры, если она не буква.
- \begin{LongCodePar}
- /* getword: принимает следующее слово или литеру из ввода */
- int getword(char *word, int lim)
- {
- int c, getch(void);
- void ungetch(int);
- char *w = word;
- while (isspace(c = getch()))
- ;
- if (c != EOF)
- *w++ = c;
- if (!isalpha(c)) {
- *w = '\0';
- return c;
- }
- for ( ; --lim > 0; w++)
- if (!isalnum(*w = getch())) {
- ungetch(*w);
- break;
- }
- *w = '\0';
- return word[0];
- }
- \end{LongCodePar}
- %
- % исправлена опечатка
- % в оригинале не хватало закрывающей скобки в
- % while (isspace(c = getch())
- %
- Функция \verb|getword| обращается к \verb|getch| и \verb|ungetch|, которые мы
- написали в гл.~\ref{chapt:functions_and_program_structure}. При завершении
- набора букв-цифр оказывается, что \verb|getword| взяла лишнюю литеру. Обращение
- к \verb|ungetch| позволяет вернуть её назад во входной поток. В \verb|getword|
- используются также \verb|isspace| -- для пропуска пробельных литер,
- \verb|isalpha| -- для идентификации букв и \verb|isalnum| -- для распознавания
- букв-цифр. Все они описаны в стандартном головном файле \verb|<ctype.h>|.
- \paragraph{Упражнение 6.1.} Наша версия \verb|getword| не обрабатывает должным
- образом знак подчёркивания, стринговые константы, комментарии и управляющие
- строки препроцессора. Напишите более совершенный вариант программы.
- \section{Указатели на структуры}
- \index{структура!указатель на неё}%
- \index{указатель!на структуру}%
- Для иллюстрации некоторых моментов, касающихся указателей на структуры и
- массивов структур, перепишем программу подсчёта ключевых слов, пользуясь для
- получения элементов массива вместо индексов указателями.
- \index{функция!binsearch@\texttt{binsearch}}%
- Внешняя декларация массива \verb|keytab| остаётся без изменения, а
- \verb|main| и \verb|binsearch| нужно модифицировать.
- \begin{LongCodePar}
- #include <stdio.h>
- #include <ctype.h>
- #include <string.h>
- #define MAXWORD 100
- int getword(char *, int);
- struct key *binsearch(char *, struct key *, int);
- /* подсчёт ключевых слов Си; версия с указателями */
- main()
- {
- char word[MAXWORD];
- struct key *p;
- while (getword(word, MAXWORD) != EOF)
- if (isalpha(word[0]))
- if ((p=binsearch(word, keytab, NKEYS)) != NULL)
- p->count++;
- for (p = keytab; p < keytab + NKEYS; p++)
- if (p->count > 0)
- printf("%4d %s\n", p->count, p->word);
- return 0;
- }
- /* binsearch: найти слово в tab[0]...tab[n-1] */
- struct key *binsearch(char *word, struct key *tab, int n)
- {
- int cond;
- struct key *low = &tab[0];
- struct key *high = &tab[n];
- struct key *mid;
- while (low < high) {
- mid = low + (high-low) / 2;
- if ((cond = strcmp(word, mid->word)) < 0)
- high = mid;
- else if (cond > 0)
- low = mid + 1;
- else
- return mid;
- }
- return NULL;
- }
- \end{LongCodePar}
- Некоторые детали этой программы требуют пояснений. Первое, описание функции
- \verb|binsearch| должно отражать тот факт, что она возвращает указатель на
- \verb|struct key|, а не целое; соответствующие изменения коснулись как прототипа
- функции, так и её заголовка. Если \verb|binsearch| находит слово, то она выдаёт
- указатель на него, в противном случае она возвращает \verb|NULL|.
- \index{указатели!арифметика с}%
- Второе, к элементам \verb|keytab| доступ осуществляется в нашей программе через
- указатели. Это потребовало значительных изменений в \verb|binsearch|.
- Инициализаторами для \verb|low| и \verb|high| теперь служат указатели на начало
- и на место сразу после конца массива.
- \index{неправильная арифметика с указателями}%
- \index{сравнение указателей}%
- \index{указатели!неправильная арифметика с}%
- \index{указатели!сравнение}%
- Вычисление положения среднего элемента с помощью формулы
- \begin{ShortCodePar}
- mid = (low+high) / 2 /* НЕВЕРНО */
- \end{ShortCodePar}
- \noindent не годится, поскольку указатели нельзя складывать.
- \index{вычитание из указателя}%
- \index{указатели!вычитание}%
- Однако к ним можно применить операцию вычитания, и так как \verb|high-low| есть
- число элементов, присваивание
- \begin{ShortCodePar}
- mid = low + (high-low) / 2
- \end{ShortCodePar}
- \noindent установит в \verb|mid| указатель на элемент, лежащий посередине между
- \verb|low| и \verb|high|.
- Самое важное при переходе на новый вариант программы -- сделать так, чтобы не
- генерировались неправильные указатели и не было попыток обращений за пределы
- массива. Проблема в том, что и \verb|&tab[-1]|, и \verb|&tab[n]| находятся вне
- границ массива. Первый адрес определённо неверен, нельзя также осуществить
- доступ и по второму адресу. По правилам языка, однако, гарантируется, что адрес
- ячейки памяти, следующей сразу за концом массива (т.е. \verb|&tab[n]|), в
- арифметике с указателями воспринимается правильно.
- В главной программе мы написали
- \begin{ShortCodePar}
- for (p = keytab; p < keytab + NKEYS; p++)
- \end{ShortCodePar}
- \noindent%
- \index{масштабирование целых в арифметике с указателями}%
- \index{структура!размер}%
- \index{указатели!коэффициент домножения целых в арифметике с}%
- Если \verb|p| -- указатель на структуру, то при выполнении операций с \verb|p|
- учитывается размер структуры. Поэтому \verb|p++| увеличит \verb|p| на такую
- величину, чтобы выйти на следующий структурный элемент массива, а проверка
- условия вовремя остановит цикл.
- Не следует, однако, полагать, что размер структуры равен сумме размеров её
- членов. Вследствие
- \index{выравнивание!ограничения по}%
- выравнивания объектов разной длины в структуре могут появляться безымянные
- <<дыры>>. Так, например, если переменная типа \verb|char| занимает один байт, а
- \verb|int| -- четыре байта, то для структуры
- \begin{ShortCodePar}
- struct {
- char c;
- int i;
- };
- \end{ShortCodePar}
- \noindent может потребоваться восемь байт, а не пять. Оператор \verb|sizeof|
- возвращает правильное значение.
- \index{программа!формат}%
- Наконец, несколько слов относительно формата программы. Если функция возвращает
- значение сложного типа, как, например, в нашем случае указатель на структуру:
- \begin{ShortCodePar}
- struct key *binsearch(char *word, struct key *tab, int n)
- \end{ShortCodePar}
- \noindent то имя функции <<высмотреть>> оказывается совсем не просто. В таких
- случаях иногда пользуются записью вида:
- \begin{ShortCodePar}
- struct key *
- binsearch(char *word, struct key *tab, int n)
- \end{ShortCodePar}
- \noindent Какой форме отдать предпочтение -- дело вкуса. Выберите ту, которая
- больше всего вам нравится.
- \section{Структуры со ссылками на себя}
- \label{sec:self_reference_structures}
- \index{программа!подсчёта!слов}%
- \index{структура!ссылающаяся на себя}%
- Предположим, что мы хотим решить более общую задачу -- написать программу,
- подсчитывающую частоту встречаемости для \emph{любых} слов входного потока. Так
- как список слов заранее не известен, мы не можем предварительно упорядочить его
- и применить бинарный поиск. Было бы неразумно пользоваться и линейным поиском
- каждого полученного слова, чтобы определять, встречалось оно ранее или нет -- в
- этом случае программа работала бы слишком медленно. (Более точная оценка: время
- работы такой программы пропорционально квадрату количества слов.) Как можно
- организовать данные, чтобы эффективно справиться со списком произвольных слов?
- Один из способов -- постоянно поддерживать упорядоченность уже полученных слов,
- помещая каждое новое слово в такое место, чтобы не нарушалась имеющаяся
- упорядоченность. Делать это передвижкой слов в линейном массиве не следует, --
- хотя бы потому, что указанная процедура тоже слишком долгая. Вместо этого мы
- воспользуемся структурой данных, называемой
- \index{бинарное дерево}%
- \index{дерево!бинарное}%
- \emph{бинарным деревом}.
- В дереве на каждое отдельное слово предусмотрен <<узел>>, который содержит:
- \begin{ShortCodeParWithCC}{\\\{\}}
- \textit{указатель на текст слова}
- \textit{счётчик числа встречаемости}
- \textit{указатель на левый сыновний узел}
- \textit{указатель на правый сыновний узел}
- \end{ShortCodeParWithCC}
- \noindent У каждого узла может быть один или два сына, или узел вообще может не
- иметь сыновей.
- Узлы в дереве располагаются так, что по отношению к любому узлу левое поддерево
- содержит только те слова, которые лексикографически меньше, чем слово данного
- узла, а правое -- слова, которые больше него. Вот как выглядит дерево,
- построенное для фразы <<now is the time for all good men to come to the aid of
- their party>> (<<настало время всем добрым людям помочь своей партии>>), по
- завершении процесса, в котором для каждого нового слова в него добавлялся новый
- узел:
- %% в окружении Verbatim моноширинным шрифтом выполнено дерево так как оно было в
- %% оригинале книги. Однако, такое представление выглядит неясным и невнятным,
- %% поэтому ниже представлен вариант дерева выполненный в синтаксисе пакета
- %% xypic. Если по какой-то причине вам ближе оригинальное представление, вы
- %% можете раскомментировать окружение Verbatim и закомментировать формулу xypic.
- % \begin{Verbatim}[samepage=true,xleftmargin=\codeIndent]
- % now
- % / \
- % is the
- % / \ / \
- % for men of time
- % / \ \ / \
- % all good party their to
- % / \
- % aid come
- % \end{Verbatim}
- {\small
- $$
- \xymatrix@C=.5em{
- &&&&&\text{now}\ar@{-}[dll]\ar@{-}[drr]&&&&\\
- &&&\text{is}\ar@{-}[dl]\ar@{-}[dr]&&&&\text{the}\ar@{-}[dll]\ar@{-}[dr]&&\\
- &&\text{for}\ar@{-}[dl]\ar@{-}[dr]&&\text{men}&
- \text{of}\ar@{-}[dr]&&&\text{time}\ar@{-}[dl]\ar@{-}[dr]&\\
- &\text{all}\ar@{-}[dl]\ar@{-}[dr]&&\text{good}&&&
- \text{party}&\text{their}&&\text{to}\\
- \text{aid}&&\text{come}&&&&&&&
- }
- $$
- }
- \noindent Чтобы определить, помещено ли уже в дерево вновь поступившее слово,
- начинают с корня, сравнивая это слово со словом из корневого узла. Если они
- совпали, то ответ на вопрос -- положительный. Если новое слово меньше слова из
- дерева, то поиск продолжается в левом поддереве‚ если больше‚ то -- в правом.
- Если же в выбранном направлении поддерева не оказалось, то этого слова в дереве
- нет, а пустующая ссылка, говорящая об отсутствии поддерева, как раз то место,
- куда нужно <<подвесить>> узел с новым словом.
- \index{рекурсия}%
- Описанный процесс по сути рекурсивен, так как поиск в любом узле использует
- результат поиска в одном из своих сыновних узлов. В соответствии с этим для
- добавления узла и печати дерева здесь наиболее естественно применить рекурсивные
- функции.
- Вернёмся к описанию узла, которое удобно представить в виде структуры с четырьмя
- компонентами:
- \begin{ShortCodePar}
- struct tnode { /* узел дерева */
- char *word; /* указатель на текст */
- int count; /* число вхождений */
- struct tnode *left; /* левый сын */
- struct tnode *right; /* правый сын */
- };
- \end{ShortCodePar}
- \noindent Приведённое рекурсивное определение узла может показаться рискованным,
- но оно правильное. Структура не может включать саму себя, но ведь
- \begin{ShortCodePar}
- struct tnode *left;
- \end{ShortCodePar}
- \noindent определяет \verb|left| как указатель на \verb|tnode|, а не сам
- \verb|tnode|.
- \index{структура!ссылающаяся на себя}%
- \index{структуры взаимно рекурсивные}%
- Иногда возникает потребность во взаимоссылающихся структурах: двух структурах,
- ссылающихся друг на друга. Приём, позволяющий справиться с этой задачей,
- демонстрирует следующий фрагмент:
- \begin{LongCodePar}
- struct t {
- ...
- struct s *p; /* p указывает на s */
- };
- struct s {
- ...
- struct t *q; /* q указывает на t */
- };
- \end{LongCodePar}
- Вся программа удивительно мала -- правда, она использует вспомогательные
- программы типа \verb|getword|, уже написанные нами.
- \index{функция!addtree@\texttt{addtree}}%
- Главная программа читает слова с помощью \verb|getword| и вставляет их в дерево
- посредством \verb|addtree|.
- \begin{LongCodePar}
- #include <stdio.h>
- #include <ctype.h>
- #include <string.h>
- #define MAXWORD 100
- struct tnode *addtree(struct tnode *, char *);
- void treeprint(struct tnode *);
- int getword(char *, int);
- /* подсчёт частоты встречаемости слов */
- main()
- {
- struct tnode *root;
- char word[MAXWORD];
- root = NULL;
- while (getword(word, MAXWORD) != EOF)
- if (isalpha(word[0]))
- root = addtree(root, word);
- treeprint(root);
- return 0;
- }
- \end{LongCodePar}
- Функция \verb|addtree| рекурсивна. Первое слово функция \verb|main| помещает на
- верхний уровень дерева (корень дерева). Каждое вновь поступившее слово
- сравнивается со словом узла и <<погружается>> или в левое, или в правое
- поддерево с помощью рекурсивного обращения к \verb|addtree|. Через некоторое
- время это слово обязательно либо совпадёт с каким-нибудь из имеющихся в дереве
- слов (в этом случае к счётчику будет добавлена $1$), либо программа встретит
- пустую ссылку, что послужит сигналом для заведения нового узла и добавления его
- к дереву. Создание нового узла сопровождается тем, что \verb|addtree| возвращает
- на него указатель, который вставляется в узел родителя.
- \begin{LongCodePar}
- struct tnode *talloc(void);
- char *strdup(char *);
- /* addtree: добавляет узел со словом w в p или ниже него */
- struct tnode *addtree(struct tnode *p, char *w)
- {
- int cond;
- if (p == NULL) { /* слово встречается впервые */
- p = talloc(); /* создаётся новый узел */
- p->word = strdup(w);
- p->count = 1;
- p->left = p->right = NULL;
- } else if ((cond = strcmp(w, p->word)) == 0)
- p->count++; /* это слово уже встречалось */
- else if (cond < 0) /* < корня левого поддерева */
- p->left = addtree(p->left, w);
- else /* > корня правого поддерева */
- p->right = addtree(p->right, w);
- return p;
- }
- \end{LongCodePar}
- Память для нового узла запрашивается с помощью программы \verb|talloc|, которая
- возвращает указатель на свободное пространство, достаточное для хранения одного
- узла дерева, а копирование нового слова в отдельное место памяти осуществляется
- с помощью \verb|strdup|. (Мы рассмотрим эти программы чуть позже.) В тот (и
- только в тот) момент, когда к дереву подвешивается новый узел, происходит
- инициализация счётчика и заполнение пустыми ссылками указателей на сыновей. Мы
- опустили (что неразумно) контроль ошибок, который должен выполняться при
- получении значений от \verb|strdup| и \verb|talloc|.
- \index{функция!treeprint@\texttt{treeprint}}%
- Функция \verb|treeprint| печатает дерево в лексикографическом порядке; для
- каждого узла она печатает его левое поддерево (все слова, которые меньше слова
- данного узла), затем само слово и, наконец, правое поддерево (слова, которые
- больше слова данного узла).
- \begin{LongCodePar}
- /* treeprint: упорядоченная печать дерева p */
- void treeprint(struct tnode *p)
- {
- if (p != NULL) {
- treeprint(p->left);
- printf("%4d %s\n", p->count, p->word);
- treeprint(p->right);
- }
- }
- \end{LongCodePar}
- \index{рекурсия}%
- Если вы не уверены, что досконально разобрались в том, как работает рекурсия,
- <<проиграйте>> действия \verb|treeprint| на дереве, приведённом выше.
- \index{эффективность}%
- Практическое замечание: если дерево <<несбалансировано>> (что бывает, когда
- слова поступают не в случайном порядке), то время работы программы может сильно
- возрасти. Худший вариант, когда слова уже упорядочены; в этом случае затраты на
- вычисления будут такими же, как при линейном поиске. Существуют обобщения
- бинарного дерева, которые не страдают этим недостатком, но здесь мы их не
- описываем.
- \index{память!распределитель}%
- \index{распределитель памяти}%
- Прежде чем окончательно оставить этот пример, стоит сделать краткое отступление
- от темы и поговорить о механизме запроса памяти. Очевидно, хотелось бы иметь
- всего лишь одну функцию, выделяющую память, даже если эта память предназначается
- для разного рода объектов. Но если одна и та же функция обеспечивает память,
- скажем, и для указателей на \verb|char|, и для указателей на
- \verb|struct tnode|, то возникают два вопроса. Первый, как справиться с
- требованием большинства машин, в которых объекты определённого типа должны быть
- выровнены (например, целые часто должны размещаться, начиная с чётных адресов)?
- И второе, как описать функцию, которая вынуждена в качестве результата выдавать
- указатели разных типов?
- \index{выравнивание!ограничения по}%
- Вообще говоря, требования, касающиеся выравнивания, можно легко выполнить за
- счёт некоторой потери памяти. Однако для этого возвращаемый указатель должен
- быть таким, чтобы удовлетворялись любые ограничения, связанные с выравниванием.
- Функция \verb|alloc|, описанная в гл.~\ref{chapt:pointers_and_arrays}, не
- гарантирует нам любое конкретное выравнивание, поэтому мы будем пользоваться
- \index{библиотечная функция!malloc@\texttt{malloc}}%
- функцией \verb|malloc| из стандартной библиотеки, которая это делает. В
- гл.~\ref{chapt:unix_system_interface} мы покажем один из способов её реализации.
- \index{оператор!приведения к типу}%
- Вопрос об описании типа таких функций, как \verb|malloc|, является камнем
- преткновения в любом языке с жёсткой проверкой типов. В Си вопрос решается
- естественным образом: \verb|malloc| объявляется как функция, которая возвращает
- указатель на \verb|void|.
- \index{преобразование!указателя}%
- \index{указатель!преобразование}%
- Полученный указатель затем явно приводится к желаемому типу. Описания
- \verb|malloc| и связанных с ней функций находятся в стандартном головном файле
- \verb|<stdlib.h>|.
- \index{функция!talloc@\texttt{talloc}}%
- Таким образом, функцию \verb|talloc| можно записать следующим образом:
- \begin{ShortCodePar}
- #include <stdlib.h>
- /* talloc: создаёт tnode */
- struct tnode *talloc(void)
- {
- return (struct tnode *) malloc(sizeof(struct tnode));
- }
- \end{ShortCodePar}
- \index{функция!strdup@\texttt{strdup}}%
- Функция \verb|strdup| просто копирует стринг, указанный в аргументе, в место,
- полученное с помощью \verb|malloc|:
- \begin{LongCodePar}
- char *strdup(char *s) /* дублирует s */
- {
- char *p;
- p = (char *) malloc(strlen(s)+1); /* +1 для '\0' */
- if (p != NULL)
- strcpy(p, s);
- return p;
- }
- \end{LongCodePar}
- \noindent Функция \verb|malloc| возвращает \verb|NULL|, если свободного
- пространства нет; \verb|strdup| передаёт это значение, оставляя заботу о выходе
- из ошибочной ситуации той программе, которая к ней обратилась.
- Память, полученную с помощью \verb|malloc|, можно освободить для повторного
- использования, обратившись к функции
- \verb|free|~(см.~гл.~\ref{chapt:input-output}~и~\ref{chapt:unix_system_interface}).
- \paragraph{Упражнение 6.2.} Напишите программу, которая читает текст
- Си-программы и печатает в алфавитном порядке все группы имён переменных, в
- которых совпадают первые 6 литер, но последующие в чём-то различаются. Не
- обрабатывайте внутренности стрингов и комментариев. Число 6 сделайте параметром,
- задаваемым в командной строке.
- \paragraph{Упражнение 6.3.} Напишите программу печати таблицы <<перекрёстных
- ссылок>>, которая будет печатать все слова документа и указывать для каждого из
- них номера строк, где оно встретилось. Программа должна игнорировать
- <<шумовые>> слова типа <<и>>, <<или>> и т.д.
- \paragraph{Упражнение 6.4.} Напишите программу, которая печатает весь набор
- различных слов, образующих входной поток, в порядке возрастания частоты их
- встречаемости. Перед каждым словом должно быть указано число вхождений.
- \section{Просмотр таблиц}
- \index{программа!поиска!в таблице}%
- В этом разделе, чтобы проиллюстрировать новые аспекты применения структур, мы
- напишем ядро пакета программ, осуществляющих вставку элементов в таблицы и их
- поиск внутри таблиц. Этот пакет -- типичный набор программ, с помощью которых
- работают с таблицами имён в любом макропроцессоре или компиляторе. Рассмотрим,
- например, инструкцию \verb|#define|. Когда встречается строка вида
- \begin{ShortCodePar}
- #define IN 1
- \end{ShortCodePar}
- \noindent имя \verb|IN| и замещающий его текст \verb|1| должны запоминаться в
- таблице. Если затем это имя \verb|IN| встретится в инструкции, например, в
- \begin{ShortCodePar}
- state = IN;
- \end{ShortCodePar}
- \noindent оно должно быть заменено на \verb|1|.
- Существуют две программы, манипулирующие с именами и замещающими их текстами.
- Это \verb|install(s, t)|, которая записывает имя \verb|s| и замещающий его текст
- \verb|t| в таблицу, где \verb|s| и \verb|t| -- стринги, и \verb|lookup(s)|‚
- осуществляющая поиск \verb|s| в таблице и возвращающая указатель на место, где
- имя \verb|s| было найдено, или \verb|NULL|, если \verb|s| в таблице не
- оказалось.
- Алгоритм основан на <<хэшировании>> (функции расстановки): поступающее имя
- свёртывается в неотрицательное число (хэш-код), которое затем используется в
- качестве индекса в массиве указателей. Каждый элемент этого массива является
- указателем на начало связанного ссылками списка блоков, описывающих имена с
- данным хэш-кодом. Если элемент массива содержит \verb|NULL|, это значит, что
- среди имён не встретилось ни одного с соответствующим хэш-кодом.
- \begin{figure}[H]
- \center{\includegraphics[width=0.5762712\linewidth]{chapt6_sec6_img0.eps}}
- \end{figure}
- Блок в списке -- это структура, содержащая указатели на имя, на замещающий текст
- и на следующий блок в списке; значение \verb|NULL| в указателе на следующий блок
- означает конец списка.
- \begin{ShortCodePar}
- struct nlist { /* элемент таблицы */
- struct nlist *next; /* ук-ль на следующий элемент */
- char *name; /* определяемое имя */
- char *defn; /* замещающий текст */
- };
- \end{ShortCodePar}
- %
- % добавлены завершающие ``;''
- % в оригинале их нет
- %
- \noindent А вот как записывается определение массива указателей:
- \begin{ShortCodePar}
- #define HASHSIZE 101
- static struct nlist *hashtab[HASHSIZE]; /* таблица ук-лей */
- \end{ShortCodePar}
- \index{функция!hash@\texttt{hash}}%
- Хэш-функция, используемая в \verb|lookup| и \verb|install|, суммирует коды литер
- имени, тем самым <<замешивая>> их, и в качестве результата выдаёт остаток от
- деления полученной суммы на размер массива указателей. Это не самый лучший
- способ получения хэш-кода, но достаточно лаконичный и эффективный.
- \begin{LongCodePar}
- /* hash: получает хэш-код по стрингу s */
- unsigned hash(char *s)
- {
- unsigned hashval;
- for (hashval = 0; *s != '\0'; s++)
- hashval = *s + 31 * hashval;
- return hashval % HASHSIZE;
- }
- \end{LongCodePar}
- \noindent Беззнаковая арифметика гарантирует, что хэш-код будет неотрицательным.
- \index{hash-таблица}%
- Хэширование порождает стартовый индекс для массива \verb|hashtab|; если
- соответствующий стринг в таблице есть, он может быть обнаружен только в списке
- блоков, на начало которого указывает элемент массива \verb|hashtab| с этим
- индексом.
- \index{функция!lookup@\texttt{lookup}}%
- Поиск осуществляется с помощью \verb|lookup|. Если \verb|lookup| находит элемент
- с заданным стрингом, то он возвращает указатель на него, если не находит, то
- возвращает \verb|NULL|.
- \begin{LongCodePar}
- /* lookup: ищет s */
- struct nlist *lookup(char *s)
- {
- struct nlist *np;
- for (np = hashtab[hash(s)]; np != NULL; np = np->next)
- if (strcmp(s, np->name) == 0)
- return np; /* нашли */
- return NULL; /* не нашли */
- }
- \end{LongCodePar}
- \noindent В \verb|for|-цикле функции \verb|lookup| для просмотра списка
- используется стандартная конструкция
- \begin{ShortCodePar}
- for (ptr = head; ptr != NULL; ptr = ptr->next)
- ...
- \end{ShortCodePar}
- \index{функция!install@\texttt{install}}%
- Функция \verb|install| обращается к \verb|lookup|, чтобы определить, имеется ли
- в наличии вставляемый стринг. Если это так, то старое определение будет заменено
- новым. В противном случае будет образован новый элемент. Если запрос памяти для
- нового элемента не может быть удовлетворён, функция \verb|install| выдаёт
- \verb|NULL|.
- \begin{LongCodePar}
- struct nlist *lookup(char *);
- char *strdup(char *);
- /* install: заносит (name, defn) в таблицу */
- struct nlist *install(char *name, char *defn)
- {
- struct nlist *np;
- unsigned hashval;
- if ((np = (lookup(name))) == NULL) { /* не найден */
- np = (struct nlist *) malloc(sizeof(*np));
- if (np == NULL || (np->name = strdup(name)) == NULL)
- return NULL;
- hashval = hash(name);
- np->next = hashtab[hashval];
- hashtab[hashval] = np;
- } else /* уже имеется */
- free((void *) np->defn); /* освобождаем прежн.defn */
- if ((np->defn = strdup(defn)) == NULL)
- return NULL;
- return np;
- }
- \end{LongCodePar}
- %
- % исправлена опечатка в оригинале не хватает закрывающей скобки
- % if ((np = (lookup(name)) == NULL) { /* не найден */
- %
- \paragraph{Упражнение 6.5.} Напишите функцию \verb|undef|, удаляющую имя и
- определение из таблицы, организация которой поддерживается функциями
- \verb|lookup| и \verb|install|.
- \paragraph{Упражнение 6.6.} Реализуйте простую версию \verb|#define|-процессора
- (без аргументов), которая использовала бы программы этого раздела и годилась бы
- для Си-программ. Вам могут помочь программы \verb|getch| и \verb|ungetch|.
- \section{Средство \texorpdfstring{\protect\Verb|typedef|}{typedef}}
- \label{sec:typedef}
- \index{декларация!typedef@\texttt{typedef}}%
- \index{typedef-декларация@\texttt{typedef}-декларация}%
- Язык Си предоставляет средство, называемое \verb|typedef|, позволяющее давать
- новые имена типам данных. Например, декларация
- \begin{ShortCodePar}
- typedef int Length
- \end{ShortCodePar}
- \noindent делает имя \verb|Length| синонимом \verb|int|. С этого момента тип
- \verb|Length| можно применять в декларациях, в операторе приведения и т.д. точно
- так же, как тип \verb|int|:
- \begin{ShortCodePar}
- Length len, maxlen;
- Length *lengths[];
- \end{ShortCodePar}
- \noindent Аналогично декларация
- \begin{ShortCodePar}
- typedef char *String
- \end{ShortCodePar}
- \noindent делает \verb|String| синонимом \verb|char *|, т.е. указателем на
- \verb|char|, и правомерным будет, например, следующее его использование:
- \begin{ShortCodePar}
- String p, lineptr[MAXLINES], alloc(int);
- int strcmp(String, String);
- p = (String) malloc(100);
- \end{ShortCodePar}
- Заметим, что объявляемый в \verb|typedef| тип стоит на месте имени переменной в
- обычной декларации, а не сразу за словом \verb|typedef|. С точки зрения
- синтаксиса слово \verb|typedef| занимает место, где обычно располагается
- спецификатор класса памяти -- \verb|extern|, \verb|static| и т.д. Имена типов
- записаны с заглавных букв для того, чтобы они выделялись.
- Для демонстрации более сложных примеров применения \verb|typedef| воспользуемся
- этим средством при задании узлов деревьев, с которыми мы уже встречались в
- данной главе.
- \begin{ShortCodePar}
- typedef struct tnode *Treeptr;
- typedef struct node { /* узел дерева: */
- char *word; /* указатель на текст */
- int count; /* число вхождений */
- Treeptr left; /* левый сын */
- Treeptr right; /* правый сын */
- } Treenode;
- \end{ShortCodePar}
- \noindent В результате создаются два новых названия типов: \verb|Treenode|
- (структура) и \verb|Treeptr| (указатель на структуру).
- \index{функция!talloc@\texttt{talloc}}%
- Теперь программу \verb|talloc| можно записать в следующем виде:
- \begin{ShortCodePar}
- Treeptr talloc(void)
- {
- return (Treeptr) malloc(sizeof(Treenode));
- }
- \end{ShortCodePar}
- Следует подчеркнуть, что декларация \verb|typedef| не создаёт новый тип, она
- лишь сообщает новое имя уже существующего типа. Никакого нового смысла эти новые
- имена не несут, они декларируют переменные в точности с теми же свойствами, как
- если бы они были объявлены напрямую без переименования типа. Фактически
- \verb|typedef| аналогичен \verb|#define| с тем лишь отличием, что, будучи
- интерпретируемым компилятором, он может справиться с такой текстовой
- подстановкой, которая не может быть обработана препроцессором.
- \index{указатель!на функцию}%
- \index{функция!указатель на}%
- Например,
- \begin{ShortCodePar}
- typedef int (*PHI)(char *, char *);
- \end{ShortCodePar}
- \noindent определяет тип \verb|PHI| как <<указатель на функцию (двух аргументов
- типа \verb|char *|), возвращающую \verb|int|>>, который, например, в программе
- сортировки, описанной в гл.~\ref{chapt:pointers_and_arrays}, можно использовать
- в таком контексте:
- \begin{ShortCodePar}
- PHI strcmp, numcmp;
- \end{ShortCodePar}
- \index{переносимость}%
- Помимо просто эстетических соображений, для привлечения \verb|typedef|
- существуют две важные причины. Первая -- параметризация программы, связанная с
- проблемой переносимости. Если с помощью \verb|typedef| объявить типы данных,
- которые, возможно, являются машинно-зависимыми, то при переносе программы на
- другую машину потребуется внести изменения только в определения \verb|typedef|.
- Одна из распространённых ситуаций -- использование \verb|typedef|-имён для
- варьирования целыми величинами.
- \index{size{\_}t@\texttt{size{\_}t}}%
- Для каждой конкретной машины это предполагает соответствующие установки
- \verb|short|, \verb|int| или \verb|long|, которые делаются аналогично установкам
- стандартных типов, например, \verb|size_t| и \verb|ptrdiff_t|.
- \index{программа!читаемость}%
- Вторая причина, побуждающая к применению \verb|typedef|, -- желание сделать
- более ясным текст программы. Тип, названный \verb|Treeptr| (от английских слов
- tree -- дерево и pointer -- указатель) более понятен, чем тот же тип, записанный
- как указатель на некоторую сложную структуру.
- \section{Объединения}
- \index{декларация!union@\texttt{union}}%
- \index{union@\texttt{union}!декларация}%
- \emph{Объединение} -- это переменная, которая может содержать (в разные моменты
- времени) объекты различных типов и размеров. Все требования относительно
- размеров и
- \index{выравнивание!ограничения по}%
- выравнивания выполняет компилятор.
- \index{переносимость}%
- Объединения позволяют хранить разнородные данные в одной и той же области памяти
- без включения в программу машинно-зависимой информации. Эти средства аналогичны
- вариантным записям в Паскале.
- Примером использования объединений мог бы послужить сам компилятор, заведующий
- таблицей символов, если предположить, что константы могут иметь тип \verb|int|,
- \verb|float| или являться указателем на стринговый литерал и иметь тип
- \verb|char *|. Значение каждой конкретной константы должно храниться в
- переменной соответствующего этой константе типа. Работать с таблицей символов
- всегда удобнее, если значения занимают одинаковую по объёму память и
- запоминаются в одном и том же месте независимо от своего типа. Цель введения в
- программу объединения -- иметь переменную, которая бы на законных основаниях
- хранила в себе значения нескольких типов. Синтаксис объединений аналогичен
- синтаксису структур. Приведём пример объединения.
- \begin{ShortCodePar}
- union u_tag {
- int ival;
- float fval;
- char *sval;
- } u;
- \end{ShortCodePar}
- Переменная \verb|u| будет достаточно большой, чтобы в ней поместилась любая
- переменная из указанных трёх типов; точный её размер зависит от реализации.
- Значение одного из этих трёх типов может быть присвоено переменной \verb|u| и
- далее использовано в выражениях, если это правомерно, т.е. если тип взятого ею
- значения совпадает с типом последнего присвоенного ей значения. Выполнение этого
- требования в каждый текущий момент -- целиком на совести программиста. В случае
- <<рассогласованности>> типов результат зависит от реализации.
- Синтаксис доступа к членам объединения следующий:
- \begin{ShortCodeParWithCC}{\\\{\}}
- \textit{имя-объединения} . \textit{член}
- \end{ShortCodeParWithCC}
- \noindent или
- \begin{ShortCodeParWithCC}{\\\{\}}
- \textit{указатель-на-объединение} -> \textit{член}
- \end{ShortCodeParWithCC}
- \noindent т.е. в точности такой, как в структурах. Если для хранения типа
- текущего значения \verb|u| использовать, скажем, переменную \verb|utype|, то
- можно написать такой фрагмент программы:
- \begin{LongCodePar}
- if (utype == INT)
- printf("%d\n", u.ival);
- else if (utype == FLOAT)
- printf("%f\n", u.fval);
- else if (utype == STRING)
- printf("%s\n", u.sval);
- else
- printf(”неверный тип %d в utype\n”, utype);
- \end{LongCodePar}
- Объединения могут входить в структуры и массивы, и наоборот. Запись доступа к
- члену объединения, находящегося в структуре (как и структуры, находящейся в
- объединении), такая же, как и для вложенных структур. Например, в массиве
- структур
- \begin{LongCodePar}
- struct {
- char *name;
- int flags;
- int utype;
- union {
- int ival;
- float fval;
- char *sval;
- } u;
- } symtab[NSYM];
- \end{LongCodePar}
- \noindent на \verb|ival| ссылаются следующим образом:
- \begin{ShortCodePar}
- symtab[i].u.ival
- \end{ShortCodePar}
- \noindent а к первой литере стринга \verb|sval| можно обратиться любым из
- следующих двух способов:
- \begin{ShortCodePar}
- *symtab[i].u.sval
- symtab[i].u.sval[0]
- \end{ShortCodePar}
- Фактически объединение -- это структура, все члены которой имеют нулевое
- смещение относительно её базового адреса, размера, который позволяет поместиться
- в ней самому большому её члену, и
- \index{выравнивание!при помощи \texttt{union}}%
- \index{union@\texttt{union}!выравнивание при помощи}%
- выравнивание которой удовлетворяет всем типам объединения.
- \index{операции над!объединениями}%
- Операции, применимые к структурам, годятся и для объединений, т.е. законны
- присваивание объединения и копирование его как единого целого, взятие адреса от
- объединения и доступ к отдельным его членам.
- Инициализировать объединение можно только значением, имеющим тип его первого
- члена; таким образом, упомянутую выше переменную \verb|u| можно инициализировать
- лишь значением типа \verb|int|.
- В гл.~\ref{chapt:unix_system_interface} (на примере программы, заведующей
- выделением памяти) мы покажем, как, применяя объединение, можно добиться, чтобы
- расположение переменной было выровнено по соответствующей границе в памяти.
- \section{Поля битов}
- \index{битовое поле}%
- \index{биты, образцы манипулирования}%
- При дефиците памяти может возникнуть необходимость запаковать несколько объектов
- в одно слово машины. Одна из обычных ситуаций, встречающаяся в задачах обработки
- таблиц символов для компиляторов, -- это объединение групп однобитовых флажков.
- Форматы некоторых данных могут от нас вообще не зависеть и диктоваться,
- например, интерфейсами с аппаратурой внешних устройств; здесь также возникает
- потребность адресоваться к частям слова.
- Вообразим себе фрагмент компилятора, который заведует таблицей символов. Каждый
- идентификатор программы имеет некоторую связанную с ним информацию, которая
- сообщает, например, представляет ли он собой ключевое слово и к какому классу,
- если это переменная, она принадлежит: внешняя и/или статическая и т.д. Самый
- компактный способ кодирования такой информации -- расположить однобитовые флажки
- в одном слове типа \verb|char| или \verb|int|.
- \index{define@\texttt{{\#}define}!вместо \texttt{enum}}%
- \index{enum@\texttt{enum}!а не \texttt{{\#}define}}%
- Один из распространённых приёмов работы с битами основан на определении набора
- <<масок>>, соответствующих позициям этих битов, как, например, в
- \begin{ShortCodePar}
- #define KEYWORD 01 /* ключевое слово */
- #define EXTERNAL 02 /* внешний */
- #define STATIC 04 /* статический */
- \end{ShortCodePar}
- \noindent или в
- \begin{ShortCodePar}
- enum { KEYWORD = 01, EXTERNAL = 02, STATIC = 04 };
- \end{ShortCodePar}
- \noindent Числа должны быть степенями двойки. Тогда доступ к битам становится
- делом <<побитовых операций>>, описанных в
- гл.~\ref{chapt:types-operators-expressions} (сдвиг, маскирование, взятие
- дополнения).
- Некоторые виды записи выражений встречаются довольно часто. Так,
- \begin{ShortCodePar}
- flags |= EXTERNAL | STATIC;
- \end{ShortCodePar}
- \noindent устанавливает 1 в соответствующих битах переменной \verb|flags|,
- \begin{ShortCodePar}
- flags &= ~(EXTERNAL | STATIC);
- \end{ShortCodePar}
- \noindent обнуляет их, а
- \begin{ShortCodePar}
- if ((flags & (EXTERNAL | STATIC)) == 0) ...
- \end{ShortCodePar}
- \noindent оценивает условие как истинное, если оба бита нулевые.
- \index{биты, образцы манипулирования}%
- Хотя научиться писать такого рода выражения не составляет труда, вместо
- побитовых логических операций можно пользоваться предоставляемым Си другим
- способом прямого определения и доступа к полям внутри слова.
- \index{битовое поле}%
- \emph{Поле-битов} (или для краткости просто \emph{поле}) -- это некоторое
- множество битов, лежащих рядом внутри одной, зависящей от реализации, единице
- памяти, которую мы будем называть <<словом>>.
- \index{битовое поле!декларация}%
- \index{декларация!поля битов}%
- Синтаксис определения полей и доступа к ним базируется на синтаксисе структур.
- Например, строки \verb|#define|, фигурировавшие выше при задании таблицы
- символов, можно заменить на определение трёх полей:
- \begin{ShortCodePar}
- struct {
- unsigned int is_keyword : 1;
- unsigned int is_extern : 1;
- unsigned int is_static : 1;
- } flags;
- \end{ShortCodePar}
- \noindent%
- \index{выравнивание!битового поля}%
- Эта запись определяет переменную \verb|flags|, которая содержит три однобитовых
- поля. Число, следующее за двоеточием, задаёт ширину поля. Поля декларированы как
- \verb|unsigned int|, чтобы они воспринимались как беззнаковые величины.
- На отдельные поля ссылаются так же, как и на члены обычных структур:
- \verb|flags.is_keyword|, \verb|flags.is_extern|, и т.д. Поля <<ведут себя>> как
- малые целые и могут участвовать в арифметических выражениях точно так же, как и
- другие целые. Таким образом, предыдущие примеры можно написать более
- естественным образом:
- \begin{ShortCodePar}
- flags.is_extern = flags.is_static = 1;
- \end{ShortCodePar}
- \noindent устанавливает 1 в соответствующие биты;
- \begin{ShortCodePar}
- flags.is_extern = flags.is_static = 0;
- \end{ShortCodePar}
- \noindent их обнуляет, а
- \begin{ShortCodePar}
- if (flags.is_extern == 0 && flags.is_static == 0)
- \end{ShortCodePar}
- \noindent проверяет их.
- Почти все технические детали, касающиеся полей, в частности, может ли поле
- перейти границу слова, зависят от реализации. Поля могут не иметь имени;
- \index{битовое поле!выравнивание}%
- с помощью безымянного поля (задаваемого только двоеточием и шириной)
- организуется пропуск нужного количества разрядов. Особая ширина, равная нулю,
- используется, когда требуется выйти на границу следующего слова.
- На одних машинах поля размещаются слева направо, на других -- справа налево. Это
- значит, что при всей полезности работы с ними, если формат данных, с которыми мы
- имеем дело, дан нам свыше, то необходимо самым тщательным образом исследовать
- порядок расположения полей; программы, зависящие от такого рода вещей, не
- переносимы. Поля можно определять только с типом \verb|int|, а для того, чтобы
- обеспечить переносимость, явно указывая \verb|signed| или \verb|unsigned|. Они
- не могут быть массивами и не имеют адресов, и, следовательно, оператор \verb|&|
- к ним не применим.
|