| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718 |
- \chapter{Функции и структура программы}
- \label{chapt:functions_and_program_structure}
- \index{модульность}%
- Функции подразделяют большие вычислительные задачи на более мелкие и позволяют
- воспользоваться тем, что уже сделано другими разработчиками, а не начинать
- создание программы каждый раз <<с нуля>>. В выбранных должным образом функциях
- <<упрятаны>> несущественные для других частей программы детали их
- функционирования, что делает программу в целом более ясной и облегчает внесение
- в неё изменений.
- Язык проектировался так, чтобы функции были эффективными и простыми в
- использовании. Обычно программы на Си состоят из большого числа небольших
- функций, а не из немногих больших. Программу можно располагать в одном или
- нескольких исходных файлах.
- \index{компиляция!раздельная}%
- Эти файлы можно компилировать отдельно, а загружать вместе, в том числе и с
- ранее откомпилированными
- \index{библиотечная функция}%
- библиотечными функциями. Процесс загрузки здесь не рассматривается поскольку он
- различен в разных системах.
- Описание и определение функции -- это та область, где стандартом ANSI в язык
- внесены самые существенные изменения. Как мы видели в
- гл.~\ref{chapt:tutorial_intro}, в описании функции теперь разрешено задавать
- типы аргументов. Синтаксис определения функции также изменён, так что теперь
- описания и определения функций соответствуют друг другу. Это позволяет
- компилятору обнаруживать много больше ошибок, чем раньше. Кроме того, если типы
- аргументов соответствующим образом описаны, то необходимые преобразования
- аргументов выполняются автоматически.
- Стандарт вносит ясность в правила, определяющие области действия имён; в
- частности, он требует, чтобы для каждого внешнего объекта было только одно
- определение. В нём обобщены средства инициализации: теперь можно
- инициализировать автоматические массивы и структуры.
- Улучшен также препроцессор Си. Он включает более широкий набор директив условной
- компиляции, предоставляет возможность из макроаргументов генерировать стринги в
- кавычках, а кроме того, содержит более совершенный механизм управления процессом
- макрорасширения.
- \section{Основные сведения о функциях}
- \label{sec:functions_basics}
- \index{программа!поиска!по образцу}%
- Начнём с того, что сконструируем программу, печатающую те строки вводимого
- текста, в которых содержится некоторый <<образец>>, заданный в виде стринга
- литер. (Эта программа представляет собой частный случай функции \verb|grep|
- системы UNIX.) Рассмотрим пример: в результате поиска образца \verb|"ould"| в
- строках текста
- \begin{ShortCodePar}
- Ah Love! could you and I with Fate conspire
- To grasp this sorry Scheme of Things entire,
- Would not we shatter it to bits -- and then
- Re-mould it nearer to the Heart's Desire!
- \end{ShortCodePar}
- \noindent мы получим
- \begin{ShortCodePar}
- Ah Love! could you and I with Fate conspire
- Would not we shatter it to bits -- and then
- Re-mould it nearer to the Heart's Desire!
- \end{ShortCodePar}
- \noindent Работа по поиску образца чётко распадается на три этапа:
- \begin{ShortCodeParWithCC}{\\\{\}}
- while (\textit{существует ещё строка})
- if (\textit{строка содержит образец})
- \textit{напечатать её}
- \end{ShortCodeParWithCC}
- Хотя все три составляющие процесса поиска можно поместить в функцию \verb|main|,
- всё же лучше сохранить приведённую структуру и каждую её часть реализовать в
- виде отдельной функции.
- \index{сокрытие информации}%
- Легче иметь дело с тремя небольшими частями, чем с одной большой, поскольку,
- если несущественные особенности реализации скрыты в функциях, вероятность их
- нежелательного воздействия друг на друга минимальна. Кроме того, оформленные в
- виде функций соответствующие части могут оказаться полезными и в других
- программах.
- Конструкция <<\verb|while (|\textit{существует ещё строка}\verb|)|>>
- реализована в \verb|getline|, см. гл.~\ref{chapt:tutorial_intro}, а фразу
- <<\textit{напечатать её}>> можно записать с помощью готовой функции
- \verb|printf|. Таким образом, нам остаётся перевести на Си только то, что
- определяет, входит ли заданный образец в строку.
- \index{функция!strindex@\texttt{strindex}}%
- Чтобы решить эту задачу, мы напишем функцию \verb|strindex(s,t)|‚ которая
- указывает место (индекс) в строке \verb|s|, где начинается строка \verb|t|, или
- $-1$‚ если \verb|s| не содержит \verb|t|. Так как в Си нумерация элементов в
- массивах начинается с нуля, отрицательное число $-1$ подходит в качестве сигнала
- неудачного поиска. Если далее нам потребуется более сложное отождествление по
- образцу, мы просто заменим \verb|strindex| на другую функцию, оставив при этом
- остальную часть программы без изменений. (Библиотечная функция \verb|strstr|
- аналогична функции \verb|strindex| и отличается от последней только тем, что
- выдаёт указатель, а не индекс.)
- \index{программа!поиска!по образцу}%
- После такого проектирования программы её <<деталировка>> оказывается очевидной.
- Мы имеем представление о программе в целом и знаем, как взаимодействуют её
- части. В нашей программе образец для поиска задаётся стрингом-литералом, что
- снижает её универсальность. В гл.~\ref{chapt:pointers_and_arrays} мы ещё
- вернёмся к проблеме инициализации литерных массивов и покажем, как образец
- сделать параметром, устанавливаемым при запуске программы.
- \index{функция!getline@\texttt{getline}}%
- Здесь приведена несколько изменённая версия функции \verb|getline|‚ и было бы
- поучительно сравнить её с версией, рассмотренной в
- гл.~\ref{chapt:tutorial_intro}.
- \begin{LongCodePar}
- #include <stdio.h>
- #define MAXLINE 1000 /* макс-ный размер вводимой строки */
- int getline(char line[], int max);
- int strindex(char sourse[], char searchfor[]);
- char pattern[] = "ould"; /* образец для поиска */
- /* найти все строки, содержащие образец */
- main()
- {
- char line[MAXLINE];
- int found = 0;
- while (getline(line, MAXLINE) > 0)
- if (strindex(line, pattern) >= 0) {
- printf("%s", line);
- found++;
- }
- return found;
- }
- /* getline: читает строку в s, возвращает длину */
- int getline(char s[], int lim)
- {
- int c, i;
- i = 0;
- while (--lim > 0 && (c=getchar()) != EOF && c != '\n')
- s[i++] = c;
- if (c == '\n')
- s[i++] = c;
- s[i] = '\0';
- return i;
- }
- /* strindex: вычисляет место t в s
- или выдаёт -1, если t нет в s */
- int strindex(char s[], char t[])
- {
- int i, j, k;
- for (i = 0; s[i] != '\0'; i++) {
- for (j=i, k=0; t[k]!='\0' && s[j]==t[k]; j++, k++)
- ;
- if (k > 0 && t[k] == '\0')
- return i;
- }
- return -1;
- }
- \end{LongCodePar}
- \index{определение!функции}%
- \index{функция!определение}%
- Определение любой функции имеет следующий вид:
- \begin{ShortCodeParWithCC}{\\\[\]}
- \textit[тип-результата имя-функции](\textit[декларации-аргументов])
- {
- \textit[декларации и инструкции]
- }
- \end{ShortCodeParWithCC}
- \noindent%
- \index{функция!пустая}%
- Отдельные части определения могут отсутствовать, как, например, в определении
- <<минимальной>> функции
- \begin{ShortCodePar}
- dummy() { }
- \end{ShortCodePar}
- \noindent которая ничего не вычисляет и ничего не выдаёт. Такая ничего не
- делающая функция в процессе разработки программы бывает полезна в качестве
- <<хранителя места>>. Если тип результата опущен‚ то предполагается, что функция
- возвращает значение типа \verb|int|.
- Любая программа -- это просто совокупность определений переменных и функций.
- Связи между функциями осуществляются через аргументы, возвращаемые значения и
- внешние переменные. В исходном файле функции разрешается располагать в любом
- порядке; исходную программу можно разбивать на любое число файлов, но так, чтобы
- ни одна из функций не оказалась разрезанной.
- \index{return-инструкция@\texttt{return}-инструкция}%
- Инструкция \verb|return| реализует механизм возврата результата от вызываемой к
- вызывающей функции. За словом \verb|return| может следовать любое выражение:
- \begin{ShortCodeParWithCC}{\\\{\}}
- return \textit{выражение};
- \end{ShortCodeParWithCC}
- \noindent Если потребуется, \textit{выражение} будет приведено к типу
- \textit{тип-результата}. Часто \textit{выражение} заключают в скобки, но они не
- обязательны.
- \index{return-инструкция@\texttt{return}-инструкция}%
- Вызывающая функция в праве проигнорировать возвращаемое значение. Более того,
- \textit{выражение} в \verb|return| может отсутствовать, и тогда вообще никакое
- значение не будет возвращено в вызывающую функцию. Управление возвращается в
- вызывающую функцию без результирующего значения также и в том случае, когда
- вычисления достигли <<конца>> (т.е. последней закрывающей фигурной скобки
- функции). Не запрещена (но должна вызывать настороженность) ситуация, когда в
- одной и той же функции одни \verb|return| имеют при себе выражения, а другие --
- не имеют. Во всех случаях, когда функция <<забыла>> передать результат в
- \verb|return|, её значение есть <<мусор>>.
- Функция \verb|main| в программе поиска по образцу выдаёт в качестве результата
- количество найденных строк. Это число доступно в той среде, из которой данная
- программа была вызвана.
- \index{компиляция!нескольких файлов}%
- Механизмы компиляции и загрузки Си-программ, расположенных в нескольких исходных
- файлах, могут различаться разных системах.
- \index{cc@\texttt{cc}}%
- В системе UNIX, например, эти работы выполняет упомянутая в
- гл.~\ref{chapt:tutorial_intro} команда \verb|cc|. Предположим, что три функции
- нашего последнего примера расположены в трёх разных файлах: \verb|main.c|,
- \verb|getline.c| и \verb|strindex.c|. Тогда команда
- \begin{ShortCodePar}
- cc main.c getline.c strindex.c
- \end{ShortCodePar}
- \noindent скомпилирует указанные файлы, поместив результат компиляции в файлы
- объектных модулей \verb|main.o|‚ \verb|getline.o| и \verb|strindex.o|,
- \index{a.out@\texttt{a.out}}%
- и затем загрузит их в исполняемый файл \verb|a.out|. Если обнаружилась ошибка,
- например, в \verb|main.c|‚ то его можно скомпилировать снова и результат
- загрузить с ранее полученными объектными файлами, выполнив следующую команду:
- \begin{ShortCodePar}
- cc main.c getline.o strindex.o
- \end{ShortCodePar}
- \noindent Команда \verb|cc| использует стандартные окончания файлов
- <<\verb|.c|>> и <<\verb|.o|>>, чтобы отличать исходные файлы от объектных.
- \paragraph{Упражнение 4.1.} Напишите функцию \verb|strrindex(s,t)|, которая
- выдаёт позицию \emph{самого правого} вхождения \verb|t| в \verb|s| или $-1$‚
- если вхождения не обнаружено.
- \section{Функции, возвращающие нецелые значения}
- В предыдущих примерах функции либо вообще не возвращали результирующих значений
- (\verb|void|), либо возвращали целые значения (\verb|int|). А как быть, когда
- результат функции должен иметь другой тип? Многие вычислительные функции, как,
- например, \verb|sqrt|‚ \verb|sin| и \verb|cos|, возвращают значения типа
- \verb|double|; другие специальные функции могут выдавать значения ещё каких-то
- типов.
- \index{функция!atof@\texttt{atof}}%
- Чтобы проиллюстрировать, каким образом сделать так, чтобы функция возвращала
- нецелое значение, напишем функцию \verb|atof(s)|, которая переводит стринг
- \verb|s| в соответствующее число с плавающей точкой двойной точности. Функция
- \verb|atof| представляет собой расширение функции \verb|atoi|, две версии
- которой были рассмотрены в
- гл.~\ref{chapt:types-operators-expressions}~и~\ref{chapt:control_flow}. Она
- имеет дело со знаком (которого может и не быть), с десятичной точкой, а также с
- целой и дробной частями, одна из которых может отсутствовать. Наша версия
- \emph{не} является высококачественной программой преобразования вводимых чисел;
- такая программа потребовала бы заметно больше памяти.
- \index{файл!головной!<stdlib.h>@\texttt{<stdlib.h>}}%
- Функция \verb|atof| входит в стандартную библиотеку программ; её описание
- содержится в головном файле \verb|<stdlib.h>|.
- Прежде всего отметим, что декларировать тип возвращаемого значения должна сама
- \verb|atof|, так как этот тип не есть \verb|int|. Указатель типа задаётся перед
- именем функции.
- \begin{LongCodePar}
- #include <ctype.h>
- /* atof: преобразование стринга s в double */
- double atof(char s[])
- {
- double val, power;
- int i, sign;
- for (i = 0; isspace(s[i]); i++)
- ; /* игнорирование левых пробельных литер */
- sign = (s[i] == '-') ? -1 : 1;
- if (s[i] == '+' || s[i] == '-')
- i++;
- for (val = 0.0; isdigit(s[i]); i++)
- val = 10.0 * val + (s[i] - '0');
- if (s[i] == '.')
- i++;
- for (power = 1.0; isdigit(s[i]); i++) {
- val = 10.0 * val + (s[i] - '0');
- power *= 10.0;
- }
- return sign * val / power;
- }
- \end{LongCodePar}
- Кроме того, важно, чтобы вызывающая программа знала, что \verb|atof| возвращает
- нецелое значение. Один из способов обеспечить это -- явно описать \verb|atof| в
- вызывающей программе.
- \index{программа!калькулятор}%
- Подобное описание демонстрируется ниже в программе простенького калькулятора
- (достаточного для проверки баланса чековой книжки), который каждую вводимую
- строку воспринимает как число, прибавляет его к текущей сумме и печатает её
- новое значение.
- \begin{LongCodePar}
- #include <stdio.h>
- #define MAXLINE 100
- /* примитивный калькулятор */
- main()
- {
- double sum, atof(char []);
- char line[MAXLINE];
- int getline(char line[], int max);
- sum = 0;
- while (getline(line, MAXLINE) > 0)
- printf("\t%g\n", sum += atof(line));
- return 0;
- }
- \end{LongCodePar}
- \noindent%
- \index{прототип функции}%
- \index{функция!прототип}%
- В декларации
- \begin{ShortCodePar}
- double sum, atof(char []);
- \end{ShortCodePar}
- \noindent говорится, что \verb|sum| -- переменная типа \verb|double|‚ а
- \verb|atof| -- функция, которая принимает аргумент (один) типа \verb|char[]| и
- возвращает результат типа \verb|double|.
- \index{несоответствие типов деклараций}%
- \index{тип!несовместимость в декларациях}%
- \index{return-инструкция@\texttt{return}-инструкция}%
- Описание и определение функции \verb|atof| должны соответствовать друг другу.
- Если в одном исходном файле сама функция \verb|atof| и обращение к ней в
- \verb|main| имеют разные типы, то это несоответствие будет зафиксировано
- компилятором как ошибка. Но если функция \verb|atof| была скомпилирована
- отдельно (что более вероятно), то несоответствие типов не будет обнаружено, и
- \verb|atof| возвратит значение типа \verb|double|, которое функция \verb|main|
- воспримет как \verb|int|, что приведёт к бессмысленному результату.
- Это последнее утверждение, вероятно, вызовет у вас удивление, поскольку ранее
- говорилось о необходимости соответствия описаний и определений.
- \index{декларация!функции!неявная}%
- \index{функция!неявная декларация}%
- Причина несоответствия, возможно, будет следствием того, что вообще отсутствует
- прототип функции, и функция неявно декларируется при первом своём появлении в
- выражении, как, например, в
- \begin{ShortCodePar}
- sum += atof(line)
- \end{ShortCodePar}
- \noindent Если в выражении встретилось имя, нигде ранее не описанное, за которым
- следует открывающая скобка, то такое имя по контексту считается именем функции,
- возвращающей результат типа \verb|int|; при этом относительно её аргументов
- ничего не предполагается. Если в декларации функции аргументы не указаны, как в
- %
- % исправлена ошибка
- % в оригинале "считается именем функции, получающей результат"
- %
- \begin{ShortCodePar}
- double atof();
- \end{ShortCodePar}
- \noindent то и в этом случае считается, что ничего об аргументах \verb|atof| не
- известно, и все проверки на соответствие её параметров будут выключены.
- \index{декларация!функции!старым способом}%
- \index{функция!в старом стиле}%
- Предполагается, что такая специальная интерпретация пустого списка позволит
- новым компиляторам транслировать старые Си-программы.
- \index{void@\texttt{void}!список аргументов}%
- Если у функции есть аргументы, опишите их, если их нет, используйте слово
- \verb|void|.
- \index{функция!atoi@\texttt{atoi}}%
- Располагая соответствующим образом описанной функцией \verb|atof|, мы можем
- написать функцию \verb|atoi|‚ преобразующую стринг литер в целое значение,
- следующим образом:
- \begin{ShortCodePar}
- /* atoi: преобразование стринга s в int с помощью atof */
- int atoi(char s[])
- {
- double atof(char s[]);
- return (int) atof(s);
- }
- \end{ShortCodePar}
- \noindent%
- \index{преобразование!\texttt{return}-инструкцией}%
- \index{тип!преобразование в \texttt{return}}%
- \index{return@\texttt{return}!преобразование типа в}%
- \index{return-инструкция@\texttt{return}-инструкция}%
- Обратите внимание на вид декларации и инструкции \verb|return|. Значение
- выражения в
- \begin{ShortCodeParWithCC}{\\\{\}}
- return \textit{выражение};
- \end{ShortCodeParWithCC}
- \noindent приводится к типу функции перед тем, как оно будет передано в качестве
- результата. Следовательно, поскольку функция \verb|atoi| возвращает значение
- \verb|int|, результат вычисления \verb|atof| типа \verb|double| в инструкции
- \verb|return| автоматически преобразуется в тип \verb|int|. При преобразовании
- возможна потеря информации, и некоторые компиляторы предупреждают об этом.
- Оператор приведения явно указывает на необходимость преобразования типа и
- подавляет любое предупреждающее сообщение.
- \paragraph{Упражнение 4.2.} Дополните функцию \verb|atof| таким образом, чтобы
- она справлялась с числами вида
- \begin{ShortCodePar}
- 123.45e-6
- \end{ShortCodePar}
- \noindent в которых в конце может стоять \verb|e| (или \verb|E|) с последующей
- экспонентой (быть может, со знаком).
- \section{Внешние переменные}
- \label{sec:external_variables}
- \index{модульность}%
- \index{переменная!внешняя}%
- Программа на Си обычно оперирует с множеством внешних объектов: переменных и
- функций. Прилагательное <<внешний>> (<<\verb|external|>>) противоположно
- прилагательному <<внутренний>> (<<\verb|internal|>>), которое относится к
- аргументам и переменным, определяемым внутри функций. Внешние переменные
- специфицируются вне функций и потенциально доступны для многих функций. Сами
- функции всегда являются внешними объектами, поскольку в Си запрещено определять
- функции внутри других функций.
- \index{связь!внешняя}%
- По умолчанию одинаковые внешние имена, используемые в разных файлах, ссылаются
- на один и тот же внешний объект (функцию). (В стандарте это называется
- \emph{соединением внешних связей} (\emph{external linkage}).) В этом смысле
- внешние переменные похожи на области \verb|COMMON| в Фортране и на переменные
- самого внешнего блока в Паскале. Позже мы покажем‚ как внешние функции и
- переменные сделать видимыми только внутри одного исходного файла.
- Поскольку внешние переменные доступны всюду, их можно использовать в качестве
- связующих данных между функциями как альтернативу связей через аргументы и
- возвращаемые значения. Для любой функции внешняя переменная доступна по её
- имени, если это имя было должным образом описано.
- Если число переменных, совместно используемых функциями, велико, связи между
- последними через внешние переменные могут оказаться более удобными и
- эффективными, чем длинные списки аргументов. Но, как отмечалось в
- гл.~\ref{chapt:tutorial_intro}, к этому заявлению следует относиться критически,
- поскольку такая практика ухудшает структуру программы и приводит к слишком
- большому числу связей между функциями по данным.
- Внешние переменные полезны, так как они имеют б\'{o}льшую область действия и
- время жизни.
- \index{переменная!автоматическая}%
- Автоматические переменные действительны только внутри функции, они возникают в
- момент входа в функцию и исчезают при выходе из неё. Внешние переменные,
- напротив, существуют постоянно, так что их значения сохраняются в интервалах
- между обращениями к функциям. Таким образом, если двум функциям приходится
- пользоваться одними и теми же данными и ни одна из них не вызывает другую, то
- часто бывает удобно оформить эти общие данные в виде внешних переменных, а не
- передавать их в функцию и обратно через аргументы.
- \index{польская запись}%
- \index{реверсная польская запись}%
- В связи с приведёнными рассуждениями разберём пример.
- \index{программа!калькулятор}%
- Поставим себе задачу написать программу-калькулятор, понимающую операторы
- \verb|+|, \verb|-|, \verb|*| и \verb|/|. Такой калькулятор легче будет написать,
- если ориентироваться на польскую, а не инфиксную запись выражений. (Обратная
- польская запись применяется в некоторых карманных калькуляторах и в языках типа
- Forth и Postscript.)
- В обратной польской записи каждый оператор следует за своими операндами.
- Выражение в инфиксной записи, скажем
- \begin{ShortCodePar}
- (1 - 2) * (4 + 5)
- \end{ShortCodePar}
- \noindent в польской записи представляется как
- \begin{ShortCodePar}
- 1 2 - 4 5 + *
- \end{ShortCodePar}
- \noindent Скобки не нужны, неоднозначности в вычислениях не бывает, поскольку
- известно, сколько операндов требуется для каждого оператора.
- Реализовать нашу программу весьма просто. Каждый операнд посылается в стек; если
- встречается оператор, то соответствующее число операндов (два в случае бинарных
- операторов) берётся из стека и выполняется операция, после чего результат
- посылается в стек. В нашем примере числа $1$ и $2$ посылаются в стек, затем они
- замещаются на их разность $-1$. Далее в стек посылаются числа $4$ и $5$, которые
- затем заменяются на их сумму, $9$. Числа $-1$ и $9$ заменяются в стеке на их
- произведение (т.е. на $-9$). Встретив литеру новая-строка, программа выталкивает
- значение из стека и печатает его.
- Программа, следовательно, состоит из цикла, обрабатывающего на каждом своём шаге
- очередной встречаемый оператор или операнд:
- \begin{LongCodeParWithCC}{\\\{\}}
- while (\textit{следующий элемент не конец-файла})
- if (\textit{число})
- \textit{послать его в стек}
- else if (\textit{оператор})
- \textit{взять из стека операнды}
- \textit{выполнить операцию}
- \textit{результат послать в стек}
- else if (\textit{новая-строка})
- \textit{взять с вершины стека число и напечатать}
- else
- \textit{ошибка}
- \end{LongCodeParWithCC}
- \index{модульность}%
- Операции <<послать в стек>> и <<взять из стека>> сами по себе тривиальны, однако
- необходимость добавления механизмов обнаружения и нейтрализации ошибок делает их
- достаточно длинными.
- \index{сокрытие информации}%
- Поэтому эти операции стоит оформить в виде функций. Также разумно иметь
- отдельную функцию, получающую очередной оператор или операнд.
- Главный вопрос, который мы ещё не рассмотрели, -- это вопрос о том, где
- расположить стек и каким функциям разрешить к нему прямой доступ. Стек можно
- расположить в функции \verb|main| и передавать сам стек и текущую позицию в нём
- в качестве аргументов
- \index{функция!pop@\texttt{pop}}%
- \index{функция!push@\texttt{push}}%
- функциям \verb|push| (<<послать в стек>>) и \verb|pop|
- (<<взять из стека>>). Но функции \verb|main| нет дела до переменных, относящихся
- к стеку, -- ей нужны только операции \verb|push| и \verb|pop|. Поэтому мы
- решили стек и связанную с ним информацию хранить во внешних переменных,
- доступных для \verb|push| и \verb|pop|, но не для \verb|main|.
- Переход от эскиза к конкретной программе достаточно лёгок. Если теперь программу
- представить как текст, расположенный в некотором исходном файле, она будет иметь
- следующий вид:
- \begin{LongCodeParWithCC}{\\\!\?}
- #include... /* могут быть в любом количестве */
- #define... /* могут быть в любом количестве */
- \textit!описания функций для? main
- main() { ... }
- \textit!внешние переменные для? push \textit!и? pop
- void push(double f) { ... }
- double pop(void) { ... }
- int getop(char s[]) { ... }
- \textit!подпрограммы, вызываемые функцией? getop
- \end{LongCodeParWithCC}
- \noindent Позже мы обсудим, как текст этой программы можно разбить на два или
- большее число файлов.
- \index{switch-инструкция@\texttt{switch}-инструкция}%
- Функция \verb|main| -- это цикл, содержащий большой переключатель, передающий
- управление на ту или иную ветвь в зависимости от типа оператора или операнда.
- Здесь представлен более типичный случай применения переключателя по сравнению с
- рассмотренным в разд.~\ref{sec:switch}.
- \index{программа!калькулятор}%
- \begin{LongCodePar}
- #include <stdio.h>
- #include <stdlib.h> /* для atof() */
- #define MAXOP 100 /* макс. размер опер-да или опер-ра */
- #define NUMBER '0' /* признак числа */
- int getop(char []);
- void push(double);
- double pop(void);
- /* калькулятор с обратной польской записью */
- main()
- {
- int type;
- double op2;
- char s[MAXOP];
- while ((type = getop(s)) != EOF) {
- switch (type) {
- case NUMBER:
- push(atof(s));
- break;
- case '+':
- push(pop() + pop());
- break;
- case '*':
- push(pop() * pop());
- break;
- case '-':
- op2 = pop();
- push(pop() - op2);
- break;
- case '/':
- op2 = pop();
- if (op2 != 0.0)
- push(pop() / op2);
- else
- printf("ошибка: деление на нуль\n");
- break;
- case '\n':
- printf("\t%.8g\n", pop());
- break;
- default:
- printf("ошибка: неизвестная операция %s\n", s);
- break;
- }
- }
- return 0;
- }
- \end{LongCodePar}
- \noindent%
- \index{вычисление, порядок}%
- \index{порядок!вычислений}%
- Так как операторы \verb|+| и \verb|*| коммутативны, порядок, в котором операнды
- берутся из стека, не важен, однако в случае операторов \verb|-| и \verb|/| левый
- и правый операнды должны различаться. Так, в
- \begin{ShortCodePar}
- push(pop() - pop()); /* НЕПРАВИЛЬНО */
- \end{ShortCodePar}
- \noindent порядок, в котором выполняются обращения к \verb|рор|, не определён.
- Чтобы гарантировать правильный порядок, необходимо первое значение из стека
- присвоить временной переменной, как это и сделано в \verb|main|.
- \begin{LongCodePar}
- #define MAXVAL 100 /* максимальная глубина стека */
- int sp = 0; /* позиция след. своб. эл-та стека */
- double val[MAXVAL]; /* стек */
- /* push: положить значение f в стек */
- void push(double f)
- {
- if (sp < MAXVAL)
- val[sp++] = f;
- else
- printf("ошибка: стек полон, %g не помещается\n", f);
- }
- /* pop: взять с вершины стека и выдать в качестве рез-та */
- double pop(void)
- {
- if (sp > 0)
- return val[--sp];
- else {
- printf("ошибка: стек пуст\n");
- return 0.0;
- }
- }
- \end{LongCodePar}
- Переменная считается внешней, если она определена вне функций. Таким образом,
- стек и индекс стека, которые должны быть доступны и для \verb|push|, и для
- \verb|pop|, определяются вне этих функций.
- \index{сокрытие информации}%
- Но \verb|main| не использует ни стек, ни позицию в стеке, и поэтому их
- представление может быть скрыто от \verb|main|.
- \index{функция!getop@\texttt{getop}}%
- Займёмся реализацией \verb|getop| -- функции, получающей следующий оператор или
- операнд. Нам предстоит решить довольно простую задачу. Более точно: требуется
- пропустить пробелы и табуляции; если следующая литера -- не цифра и не
- десятичная точка, то выдать её; в противном случае накопить цепочку цифр с
- десятичной точкой, если она есть, и выдать признак числа \verb|NUMBER| в
- качестве результата.
- \begin{LongCodePar}
- #include <ctype.h>
- int getch(void);
- void ungetch(int);
- /* getop: получает следующий оператор или операнд */
- int getop(char s[])
- {
- int i, c;
- while ((s[0] = c = getch()) == ' ' || c == '\t')
- ;
- s[1] = '\0';
- if (!isdigit(c) && c != '.')
- return c; /* не число */
- i = 0;
- if (isdigit(c)) /* накапливаем целую часть */
- while (isdigit(s[++i] = c = getch()))
- ;
- if (c == '.') /* накапливаем дробную часть */
- while (isdigit(s[++i] = c = getch()))
- ;
- s[i] = '\0';
- if (c != EOF)
- ungetch(c);
- return NUMBER;
- }
- \end{LongCodePar}
- \index{функция!getch@\texttt{getch}}%
- \index{функция!ungetch@\texttt{ungetch}}%
- Как работают функции \verb|getch| и \verb|ungetch|? Во многих случаях программа
- не может <<сообразить>>, прочла ли она всё, что требуется, пока не прочтёт
- лишнего. Так, накопление числа производится до тех пор, пока не встретится
- литера, отличная от цифры. Но это означает, что программа прочла на одну литеру
- больше, чем нужно, а именно литеру, которую нельзя включать в число.
- Эту проблему можно было бы решить при наличии обратной чтению
- \index{ввод!возврат литеры на}%
- операции <<положить-назад>>, с помощью которой можно было бы вернуть
- преждевременно прочитанную литеру. Тогда каждый раз, когда программа считает на
- одну литеру больше, чем требуется, эта операция возвращала бы её вводу, и
- поэтому остальная часть программы могла бы вести себя так, как будто эта литера
- и вовсе не читалась. К счастью, описанный механизм обратной посылки литеры легко
- моделируется с помощью пары согласованных друг с другом функций, из которых
- \verb|getch| поставляет очередную литеру из ввода, а \verb|ungetch| отправляет
- назад литеру во входной поток, так что при следующем обращении к \verb|getch| мы
- вновь её получим.
- Нетрудно догадаться, как они работают вместе. Функция \verb|ungetch| запоминает
- посылаемую назад литеру в некотором буфере, представляющем собой массив литер,
- доступный для обеих этих функций; \verb|getch| читает из буфера, если там что-то
- есть, или обращаются к \verb|getchar|, если буфер пустой. Следует предусмотреть
- индекс, указывающий на положение текущей литеры в буфере.
- Так как функции \verb|getch| и \verb|ungetch| совместно используют буфер и
- индекс, значения последних должны сохраняться между вызовами. Поэтому буфер и
- индекс должны быть внешними по отношению к этим программам, и мы можем записать
- \verb|getch|, \verb|ungetch| и общие для них переменные в следующем виде:
- \begin{LongCodePar}
- #define BUFSIZE 100
- char buf[BUFSIZE]; /* буфер для ungetch */
- int bufp = 0; /* след. свободная позиция в буфере */
- int getch(void) /* дай (возможно возвращённую) литеру */
- {
- return (bufp > 0) ? buf[--bufp] : getchar();
- }
- void ungetch(int c) /* верни литеру на ввод */
- {
- if (bufp >= BUFSIZE)
- printf("ungetch: слишком много литер\n");
- else
- buf[bufp++] = c;
- }
- \end{LongCodePar}
- %
- % в оригинале в строке 2 опечатка ungetc
- %
- \noindent Стандартная библиотека включает функцию \verb|ungetc|, обеспечивающую
- возврат одной литеры (см. гл.~\ref{chapt:input-output}). Мы же, чтобы
- проиллюстрировать более общий подход, для запоминания возвращаемых литер
- использовали массив.
- \paragraph{Упражнение 4.3.} Исходя из предложенной нами схемы, дополните
- программу-калькулятор таким образом, чтобы она <<понимала>> оператор взятия
- модуля (\verb|%|) и отрицательные числа.
- \paragraph{Упражнение 4.4.} Добавьте команды, с помощью которых можно было бы
- печатать верхний элемент стека (с сохранением его на стеке), дублировать его на
- стеке, менять местами два верхних элемента стека. Введите команду очистки стека.
- \paragraph{Упражнение 4.5.} Предусмотрите возможность использования в программе
- библиотечных функций \verb|sin|, \verb|exp| и \verb|pow|. См. библиотеку
- \verb|<math.h>| в приложении~\ref{apx:stdlib} (разд.~\ref{apx:sect:math_h}).
- \paragraph{Упражнение 4.6.} Введите команды для работы с переменными (легко
- обеспечить до 26 переменных, каждая из которых имеет имя, представленное одной
- буквой латинского алфавита). Добавьте переменную, предназначенную для хранения
- самого последнего из напечатанных значений.
- \paragraph{Упражнение 4.7.} Напишите программу \verb|ungets(s)|, возвращающую
- стринг \verb|s| во входной поток. Должна ли \verb|ungets| <<знать>> что-либо о
- переменных \verb|buf| и \verb|bufp| или ей достаточно пользоваться только
- функцией \verb|ungetch|?
- \paragraph{Упражнение 4.8.} Предположим, что число литер, возвращаемых назад, не
- превышает 1. Модифицируйте с учётом этого факта функции \verb|getch| и
- \verb|ungetch|.
- \paragraph{Упражнение 4.9.} В наших функциях не предусмотрена возможность
- возврата \verb|EOF|. Подумайте, что надо сделать, чтобы можно было возвращать
- \verb|EOF|, и скорректируйте соответственно программу.
- \paragraph{Упражнение 4.10.} В основу программы калькулятора можно положить
- применение функции \verb|getline|, которая читает целиком строку; при этом
- отпадает необходимость в \verb|getch| и \verb|ungetch|. Напишите программу,
- реализующую этот подход.
- \section{Правила областей действия}
- \index{компиляция!раздельная}%
- Функции и внешние переменные, из которых состоит Си-программа, каждый раз
- компилировать все вместе нет никакой необходимости. Исходный текст можно хранить
- в нескольких файлах. Ранее скомпилированные программы можно загружать из
- \index{библиотечная функция}%
- библиотек. В связи с этим возникают следующие вопросы:
- \begin{itemize}
- \item Как писать декларации, чтобы на протяжении компиляции используемые
- переменные были должным образом описаны?
- \item В каком порядке располагать декларации, чтобы во время загрузки все
- части программы оказались связаны нужным образом?
- \item Как организовать декларации, чтобы для нескольких одноимённых деклараций
- была только одна копия?
- \item Как инициализировать внешние переменные?
- \end{itemize}
- \noindent Начнём с того, что разобьём программу-калькулятор на несколько файлов.
- Конечно, эта программа слишком мала, чтобы её стоило разбивать на файлы, однако
- разбиение нашей программы позволит продемонстрировать проблемы, возникающие в
- больших программах.
- \index{область действия!правила определения}%
- \emph{Областью действия} имени считается часть программы, в которой это имя
- можно использовать.
- \index{область действия!автоматических переменных}%
- Для автоматических переменных, описанных в начале функции,
- областью действия является функция, в которой они описаны. Локальные переменные
- разных функций, имеющие, однако, одинаковые имена, никак не связаны друг с
- другом. То же утверждение справедливо и в отношении параметров функции, которые
- фактически являются локальными переменными.
- \index{область действия!внешних объектов}%
- Область действия внешней переменной или функции простирается от точки программы,
- где она декларирована‚ до конца файла, подлежащего компиляции. Например, если
- \verb|main|, \verb|sp|, \verb|val|, \verb|push| и \verb|pop| определены в одном
- файле в указанном порядке, т.е.
- \begin{ShortCodePar}
- main() { ... }
- int sp = 0;
- double val[MAXVAL];
- void push(double f) { ... }
- double pop(void) { ... }
- \end{ShortCodePar}
- \noindent то к переменным \verb|sp| и \verb|val| можно адресоваться из
- \verb|push| и \verb|pop| просто по их именам; никаких дополнительных деклараций
- для них не требуется. Заметим, что в \verb|main| эти имена не видимы так же, как
- и сами \verb|push| и \verb|pop|.
- \index{спецификатор!extern@\texttt{extern}}%
- \index{extern@\texttt{extern}}%
- С другой стороны, если на внешнюю переменную нужно сослаться до того, как она
- определена, или если она определена в другом файле, то её декларация должна быть
- помечена словом \verb|extern|.
- \index{декларация!а не определение}%
- Важно отличать \emph{декларацию (описание)} внешней переменной от её
- \emph{определения}. Декларация объявляет свойства переменной (прежде всего её
- тип), а определение, кроме того, приводит к выделению для неё памяти. Если
- строки
- \begin{ShortCodePar}
- int sp;
- double val[MAXVAL];
- \end{ShortCodePar}
- \noindent расположены вне всех функций, то они \emph{определяют} внешние
- переменные \verb|sp| и \verb|val|, т.е. отводят для них память, и, кроме того,
- служат декларациями для остальной части исходного файла. А вот строки
- \begin{ShortCodePar}
- extern int sp;
- extern double val[];
- \end{ShortCodePar}
- \noindent декларируют для остальной части файла, что \verb|sp| -- переменная
- типа \verb|int| и что \verb|val| -- массив типа \verb|double| (размер которого
- определён где-то в другом месте); при этом ни переменная, ни массив не
- создаются, и память им не отводится.
- На всю совокупность файлов, из которых состоит исходная программа, для каждой
- внешней переменной должно быть одно-единственное \emph{определение}; другие
- файлы, чтобы получить доступ к внешней переменной, должны иметь в себе
- декларацию \verb|extern|. (Впрочем, декларацию \verb|extern| можно поместить и в
- файл, в котором содержится определение.) В определениях массивов необходимо
- указывать их размеры, что же касается деклараций \verb|extern|, то здесь они не
- обязательны.
- \index{инициализация!переменных!внешних}%
- Инициализировать внешнюю переменную можно только в определении.
- Хотя маловероятно, что нашу программу кто-нибудь станет организовывать таким
- способом, но мы определим \verb|push| и \verb|pop| в одном файле, а \verb|val| и
- \verb|sp| -- в другом, где их и инициализируем. При этом для установления
- связей понадобятся такие определения и декларации:
- {\setlength{\parindent}{\codeIndent}
- \textit{В файле 1:}
- }
- \begin{Verbatim}[samepage=true,xleftmargin=\doubleCodeIndent]
- extern int sp;
- extern double val[];
- void push(double f) { ... }
- double pop(void) { ... }
- \end{Verbatim}
- {\setlength{\parindent}{\codeIndent}
- \textit{В файле 2:}
- }
- \begin{Verbatim}[samepage=true,xleftmargin=\doubleCodeIndent]
- int sp = 0;
- double val[MAXVAL];
- \end{Verbatim}
- \noindent Поскольку декларации \verb|extern| находятся в начале \textit{файла 1}
- и вне определений функций, их действие распространяется на все функции, причём
- одного набора деклараций достаточно для всего \textit{файла 1}. Та же
- организация \verb|extern|-деклараций необходима и в случае, когда программа
- состоит из одного файла, но определения \verb|sp| и \verb|val| расположены после
- их использования.
- \section{Головные файлы}
- \index{файл!головной}%
- Теперь представим себе, что компоненты программы-калькулятора существенно
- б\'{o}льших размеров, и зададимся вопросом, как в этом случае распределить их
- по нескольким файлам. Программу \verb|main| поместим в файл, который мы назовём
- \verb|main.c|; \verb|push|, \verb|pop| и их переменные расположим во втором
- файле, \verb|stack.c|; а \verb|getop| -- в третьем, \verb|getop.c|. Наконец,
- \verb|getch| и \verb|ungetch| разместим в четвёртом файле \verb|getch.c|; мы
- отделили их от остальных функций, поскольку в реальной программе они будут
- получены из заранее скомпилированной библиотеки.
- Существует ещё один момент, о котором следует предупредить читателя, --
- определения и декларации совместно используются несколькими файлами. Насколько
- это возможно, мы бы хотели централизовать описания так, чтобы для них
- существовала только одна копия. Тогда программу в процессе её развития будет
- легче и исправлять, и поддерживать в правильном состоянии. Для этого общую
- информацию расположим в \emph{головном файле} \verb|calc.h|‚ который будем по
- мере необходимости включать в другие файлы. (Строка \verb|#include| описывается
- в разд.~\ref{sec:c_preprocessor}.) В результате получим программу, файловая
- структура которой показана на следующей странице.
- Неизбежен компромисс между стремлением, чтобы каждый файл владел только той
- информацией, которая ему необходима для работы, и тем, что на практике иметь
- дело с большим количеством головных файлов довольно трудно. Для программ, не
- превышающих некоторого среднего размера, вероятно, лучше всего иметь один
- головной файл, в котором собраны вместе все объекты, каждый из которых
- используется в двух различных файлах; так мы здесь и поступили. Для программ
- б\'{o}льших размеров потребуется более сложная организация с б\'{o}льшим числом
- головных файлов.
- \section{Статические переменные}
- \index{память!статическая|(}%
- Переменные \verb|sp| и \verb|val| в файле \verb|stack.c|, а также \verb|buf| и
- \verb|bufp| в \verb|getch.c| находятся в личном пользовании функций этих файлов,
- и нет смысла открывать к ним доступ кому-либо ещё.
- \index{static@\texttt{static}!декларация функции}%
- \index{static@\texttt{static}!спецификатор класса памяти}%
- \index{static-переменные@\texttt{static}-переменные!внешние}%
- Указание \verb|static|, применённое к внешней переменной или функции,
- ограничивает область действия соответствующего объекта концом файла. Это способ
- скрыть имена. Так, переменные \verb|buf| и \verb|bufp| должны быть внешними,
- поскольку их совместно используют функции \verb|getch| и \verb|ungetch|, но их
- следует сделать невидимыми для <<пользователей>> функций \verb|getch| и
- \verb|ungetch|.
- \begin{center}
- \footnotesize
- \begin{tabular}{lllll}
- &&\Verb| |calc.h:&&\\
- \cline{3-3}
- &&\multicolumn{1}{|l|}{\Verb|\#define NUMBER '0'\ \ \ \ |}&&\\
- &&\multicolumn{1}{|l|}{\Verb|void push(double);|}&&\\
- &&\multicolumn{1}{|l|}{\Verb|double pop(void);|}&&\\
- &&\multicolumn{1}{|l|}{\Verb|int getop(char[]);|}&&\\
- &&\multicolumn{1}{|l|}{\Verb|int getch(void);|}&&\\
- &&\multicolumn{1}{|l|}{\Verb|void ungetch(int);|}&&\\
- \cline{3-3}
- &&&&\\
- \Verb| |main.c:&&\Verb| |getop.c:&&\Verb| |stack.c:\\
- \cline{1-1}\cline{3-3}\cline{5-5}
- \multicolumn{1}{|l|}{\Verb|\#include <stdio.h>\ \ \ \ |}&&
- \multicolumn{1}{|l|}{\Verb|\#include <stdio.h>|}&&
- \multicolumn{1}{|l|}{\Verb|\#include <stdio.h>\ \ \ \ |}\\
- \multicolumn{1}{|l|}{\Verb|\#include <stdlib.h>|}&&
- \multicolumn{1}{|l|}{\Verb|\#include <ctype.h>|}&&
- \multicolumn{1}{|l|}{\Verb|\#include ''calc.h''|}\\
- \multicolumn{1}{|l|}{\Verb|\#include ''calc.h''|}&&
- \multicolumn{1}{|l|}{\Verb|\#include ''calc.h''|}&&
- \multicolumn{1}{|l|}{\Verb|\#define MAXVAL 100|}\\
- \multicolumn{1}{|l|}{\Verb|\#define MAXOP 100|}&&
- \multicolumn{1}{|l|}{\Verb|getop() \{|}&&
- \multicolumn{1}{|l|}{\Verb|int sp = 0;|}\\
- \multicolumn{1}{|l|}{\Verb|main() \{|}&&
- \multicolumn{1}{|l|}{\Verb|\ \ \ \ \ \ ... |}&&
- \multicolumn{1}{|l|}{\Verb|double val[MAXVAL];|}\\
- \multicolumn{1}{|l|}{\Verb|\ \ \ \ \ ... |}&&
- \multicolumn{1}{|l|}{\Verb|\}|}&&
- \multicolumn{1}{|l|}{\Verb|void push(double) \{|}\\
- \cline{3-3}
- \multicolumn{1}{|l|}{\Verb|\}|}&&
- &&
- \multicolumn{1}{|l|}{\Verb|\ \ \ \ \ ... |}\\
- \cline{1-1}
- &&
- &&
- \multicolumn{1}{|l|}{\Verb|\}|}\\
- &&
- \Verb| |getch.c:&&
- \multicolumn{1}{|l|}{\Verb|double pop(void) \{|}\\
- \cline{3-3}
- &&
- \multicolumn{1}{|l|}{\Verb|\#include <stdio.h>|}&&
- \multicolumn{1}{|l|}{\Verb|\ \ \ \ \ ... |}\\
- &&
- \multicolumn{1}{|l|}{\Verb|\#define BUFSIZE 100|}&&
- \multicolumn{1}{|l|}{\Verb|\}|}\\
- \cline{5-5}
- &&\multicolumn{1}{|l|}{\Verb|char buf[BUFSIZE];|}&&\\
- &&\multicolumn{1}{|l|}{\Verb|int bufp = 0;|}&&\\
- &&\multicolumn{1}{|l|}{\Verb|int getch(void) \{|}&&\\
- &&\multicolumn{1}{|l|}{\Verb|\ \ \ \ \ ... |}&&\\
- &&\multicolumn{1}{|l|}{\Verb|\}|}&&\\
- &&\multicolumn{1}{|l|}{\Verb|void ungetch(int) \{|}&&\\
- &&\multicolumn{1}{|l|}{\Verb|\ \ \ \ \ ... |}&&\\
- &&\multicolumn{1}{|l|}{\Verb|\}|}&&\\
- \cline{3-3}
- &&&&\\
- \end{tabular}
- \end{center}
- %
- % в оригинале в схеме файлов есть опечатки с направлением фигурных скобок
- % тут они исправлены
- %
- \index{спецификатор!static@\texttt{static}}%
- \index{static@\texttt{static}!спецификатор класса памяти}%
- Статическая память специфицируется словом \verb|static|‚ которое помещается
- перед обычным описанием. Если рассматриваемые нами две функции и две переменные
- компилируются в одном файле, как в показанном ниже примере:
- \begin{ShortCodePar}
- static char buf[BUFSIZE]; /* буфер для ungetch */
- static int bufp = 0; /* след. своб. позиция в buf */
- int getch(void) { ... }
- void ungetch(int c) { ... }
- \end{ShortCodePar}
- \noindent то никакая другая программа не будет иметь доступ ни к \verb|buf|, ни
- \verb|bufp|, и этими именами можно свободно пользоваться в других файлах для
- совсем иных целей. Точно так же, помещая указание \verb|static| перед
- декларациями переменных \verb|sp| и \verb|val|, с которыми работают только
- \verb|push| и \verb|pop|, мы можем скрыть их от остальных функций.
- \index{static@\texttt{static}!декларация функции}%
- Указание \verb|static| чаще всего используется для переменных, но с равным
- успехом его можно применять и к функциям. Обычно имена функций глобальны и
- видимы из любого места программы. Если же функция помечена словом \verb|static|,
- то её имя становится невидимым вне файла, в котором она определена.
- \index{static-переменные@\texttt{static}-переменные!внутренние}%
- Декларацию \verb|static| можно использовать и для внутренних переменных. Как и
- автоматические переменные, внутренние статические переменные локальны в
- функциях, но в отличие от автоматических они не возникают только на период
- активации функции, а существуют постоянно. Это значит, что для внутренних
- статических переменных отводится постоянная память (так же, как и для команд
- самой функции).
- \index{память!статическая|)}%
- \paragraph{Упражнение 4.11.} Модифицируйте функцию \verb|getop| так, чтобы
- отпала необходимость в функции \verb|ungetch|. Совет: используйте внутреннюю
- статическую переменную.
- \section{Регистровые переменные}
- \index{спецификатор!register@\texttt{register}}%
- \index{эффективность}%
- \index{register@\texttt{register}}%
- Спецификация \verb|register| в декларации сообщает компилятору, что данная
- переменная будет использоваться интенсивно. Идея состоит в том, чтобы переменные
- со спецификацией \verb|register| разместить на регистрах машины, благодаря чему
- программа, возможно, станет более короткой и быстрой. Однако компилятор имеет
- право проигнорировать это указание.
- Вот два примера деклараций с \verb|register|:
- \begin{ShortCodePar}
- register int x;
- register char c;
- \end{ShortCodePar}
- Спецификация \verb|register| может применяться только к автоматическим
- переменным и к формальным параметрам функции. Для последних спецификации
- \verb|register| задаются в списке параметров, как, например, в
- \begin{ShortCodePar}
- f(register unsigned m, register long n)
- {
- register int i;
- ...
- }
- \end{ShortCodePar}
- На практике существуют ограничения на \verb|register|-переменные, связанные с
- возможностями аппаратуры. Только небольшое число переменных каждой функции, и
- причём только определённых типов, могут располагаться в регистрах. Избыточные
- указания \verb|register| ни на что не влияют, так как эти указания игнорируются
- в отношении переменных, которым не хватило регистров, или которым они по
- каким-то причинам не были выделены. Кроме того, применительно к регистровой
- переменной независимо от того, выделен на самом деле регистр для неё или нет, не
- определено понятие адреса (см. гл.~\ref{chapt:pointers_and_arrays}). Конкретные
- ограничения на количество и типы регистровых переменных зависят от машины.
- \section{Блочная структура}
- \index{блок}%
- \index{блок!структура}%
- Поскольку функции в Си нельзя определять внутри других функций, он не является
- языком, допускающим блочную структуру программы в том смысле, как они разрешены
- в Паскале и подобных ему языках. Но переменные внутри функций можно определять в
- блочно-структурной манере.
- \index{составная инструкция}%
- \index{инструкция!составная}%
- \index{фигурные скобки}%
- Декларации переменных (вместе с инициализацией)
- разрешено помещать не только в начале функции, но и после \emph{любой} левой
- фигурной скобки, открывающей составную инструкцию.
- \index{имени затенение}%
- Переменная, описанная таким способом, <<затеняет>> переменные с тем же именем,
- расположенные в объемлющих блоках, и существует вплоть до соответствующей правой
- фигурной скобки. Например, в
- \begin{ShortCodePar}
- if (n > 0) {
- int i; /* описание новой переменной i */
- for (i = 0; i < n; i++)
- ...
- }
- \end{ShortCodePar}
- \noindent областью действия переменной \verb|i| является ветвь \verb|if|,
- выполняемая при \verb|n > 0|; и эта переменная никакого отношения к любым
- \verb|i|, расположенным вне данного блока, не имеет.
- \index{инициализация}%
- \index{блок!инициализация}%
- \index{инициализация!в блоке}%
- Автоматические переменные, декларируемые и инициализируемые в блоке,
- инициализируются каждый раз при входе в блок. Переменные \verb|static|
- инициализируются только один раз при первом входе в блок.
- Автоматические переменные и формальные параметры также <<затеняют>> внешние
- переменные и функции с теми же именами. Например, в
- \begin{ShortCodePar}
- int x;
- int y;
- f(double x)
- {
- double y;
- ...
- }
- \end{ShortCodePar}
- \noindent ссылки на \verb|x| внутри функции \verb|f| рассматриваются как
- параметр типа \verb|double|, в то время как вне \verb|f| они отсылают нас к
- внешней переменной типа \verb|int|. То же самое можно сказать и о переменной
- \verb|y|.
- Конечно, это вопрос стиля программирования, использовать ли одни и те же имена
- для разных переменных. Однако потенциальная опасность получить ошибку при
- <<перекрытии>> имён столь велика, что лучше этого избегать.
- \section{Инициализация}
- \index{инициализатор}%
- \index{инициализация}%
- Мы уже много раз упоминали об инициализации, но всегда по случаю, в ходе
- обсуждения других вопросов. В этом разделе мы суммируем все правила,
- определяющие инициализацию памяти различных классов.
- \index{инициализация!по умолчанию}%
- \index{инициализация!переменных!внешних}%
- \index{инициализация!переменных!статических}%
- \index{по умолчанию!инициализация}%
- При отсутствии явной инициализации для внешних и статических переменных
- гарантируется их обнуление; автоматические и регистровые переменные имеют
- неопределённые начальные значения (<<мусор>>).
- Скалярные переменные можно инициализировать в их определениях, помещая после
- имени знак \verb|=| и соответствующее выражение.
- \begin{ShortCodePar}
- int x = 1;
- char squote = '\'';
- long day = 1000L * 60L * 60L * 24L;/* день в миллисекундах */
- \end{ShortCodePar}
- %
- % в целях выравнивания в предыдущем блоке кода
- % строка "long day = 1000L * 60L * 60L * 24L; /* день в миллисекундах */"
- % заменена на "long day = 1000L * 60L * 60L * 24L;/* день в миллисекундах */"
- % данное изменение не влияет на смысл кода
- %
- \noindent Для внешних и статических переменных инициализирующие выражения должны
- быть константными, при этом инициализация осуществляется только один раз до
- начала выполнения программы.
- \index{инициализация!переменных!автоматических}%
- Инициализация автоматических и регистровых переменных выполняется каждый раз при
- входе в функцию или блок. Для таких переменных инициализирующее выражение -- не
- обязательно константное. Это может быть любое выражение, использующее ранее
- определённые значения, включая даже и вызовы функций. Например, в программе
- бинарного поиска, описанной в разд.~\ref{sec:else_if}, инициализации можно
- записать в виде
- \begin{ShortCodePar}
- int binsearch(int x, int v[], int n)
- {
- int low = 0;
- int high = n - 1;
- int mid;
- ...
- }
- \end{ShortCodePar}
- \noindent а не как
- \begin{ShortCodePar}
- int low, high, mid;
- low = 0;
- high = n - 1;
- \end{ShortCodePar}
- \noindent%
- \index{программа!читаемость}%
- В сущности, инициализация автоматической переменной -- это более
- короткая запись инструкции присваивания. Какая запись предпочтительнее -- в
- основном дело вкуса. До сих пор мы пользовались главным образом явными
- присваиваниями, поскольку инициализация в декларациях менее заметна и дальше
- отстоит от места использования переменной.
- \index{массив!размер по умолчанию}%
- \index{инициализация!массива}%
- \index{по умолчанию!размер массива}%
- \index{фигурные скобки}%
- Массив можно инициализировать в его определении с помощью заключённого в
- фигурные скобки списка инициализаторов, разделённых запятыми. Например, чтобы
- инициализировать массив \verb|days|, элементы которого суть количества дней в
- каждом месяце, можно написать
- {\setlength{\codeIndent}{\codeIndentBk-0.5em}
- \begin{ShortCodePar}
- int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
- \end{ShortCodePar}
- \setlength{\codeIndent}{\codeIndentBk}}
- \noindent Если размер массива не указан, то длину массива компилятор вычисляет
- по числу заданных инициализаторов; в нашем случае их количество равно $12$.
- Если количество инициализаторов меньше числа, указанного в определении длины
- массива, то для внешних, статических и автоматических переменных оставшиеся
- элементы будут нулевыми. Задание слишком большого числа инициализаторов
- считается ошибкой. В языке нет возможности ни задавать кратность для
- инициализатора, ни инициализировать средние элементы массива без задания всех
- предшествующих значений.
- %%
- %% исправлена опечатка
- %% в оригинале "инициализовать"
- %%
- \index{инициализация!стринговой константой}%
- Инициализация литерных массивов -- особый случай: вместо конструкции с фигурными
- скобками и запятыми можно использовать стринг литер. Например, возможна такая
- запись:
- \begin{ShortCodePar}
- int pattern[] = "ould";
- \end{ShortCodePar}
- \noindent что представляет собой более короткий эквивалент записи
- \begin{ShortCodePar}
- int pattern[] = {'o', 'u', 'l', 'd', '\0'};
- \end{ShortCodePar}
- \noindent В данном случае размер массива равен пяти (четыре обычные литеры и
- завершающая литера \verb|'\0'|).
- \section{Рекурсия}
- \index{рекурсия}%
- В Си допускается рекурсивное обращение к функциям, т.е. функция может обращаться
- сама к себе, прямо или косвенно. Рассмотрим печать числа в виде стринга литер.
- Как мы упоминали ранее, цифры генерируются в обратном порядке -- младшие цифры
- получаются раньше старших, а печататься они должны в правильной
- последовательности.
- Проблему можно решить двумя способами. Первый -- запомнить цифры в некотором
- массиве в том порядке, как они получались, а затем напечатать их в обратном
- порядке; так это и было сделано в \verb|itoa|, приведённой в
- разд.~\ref{sec:do_while}.
- \index{функция!printd@\texttt{printd}}%
- Второй способ -- воспользоваться рекурсией, при
- которой \verb|printd| сначала вызывает себя, чтобы напечатать все старшие цифры,
- и затем печатает последнюю младшую цифру. Эта программа, как и предыдущий её
- вариант, работает неправильно в случае самого большого по модулю отрицательного
- числа.
- \begin{LongCodePar}
- #include <stdio.h>
- /* printd: печатает n как целое десятичное число */
- void printd(int n)
- {
- if (n < 0) {
- putchar('-');
- n = -n;
- }
- if (n / 10)
- printd(n / 10);
- putchar(n % 10 + '0');
- }
- \end{LongCodePar}
- Когда функция рекурсивно обращаются сама к себе, каждое следующее обращение
- сопровождается получением ею нового полного комплекта автоматических переменных,
- независимых от предыдущих комплектов. Так, в обращении \verb|printd(123)| при
- первом вызове аргумент \verb|n = 123|, при втором -- \verb|printd| получает
- аргумент $12$, при третьем вызове -- значение $1$. Функция \verb|printd| на
- третьем уровне вызова печатает $1$ и возвращается на второй уровень, после чего
- она печатает цифру $2$ и возвращается на первый уровень. Здесь она печатает $3$
- и заканчивает работу.
- Следующий хороший пример рекурсии -- это
- \index{быстрая сортировка}%
- \index{программа!сортировки}%
- \index{сортировка!численная}%
- \index{числа!сортировка}%
- быстрая сортировка, предложенная
- \index{Хоар Ч. А. Р.}%
- Ч.А.Р. Хоаром в 1962 г. Для заданного массива выбирается один элемент, который
- разбивает остальные элементы на два подмножества -- те, что меньше, и те, что не
- меньше него. Та же процедура рекурсивно применяется и к двум полученным
- подмножествам. Если в подмножестве менее двух элементов, то сортировать нечего,
- и рекурсия завершается.
- %%
- %% исправлена неточность имени
- %% в оригинале Charles Antony Richard Hoare
- %% был назван "К.А.Р.Хоором"
- %% можно спорить о трансфонетизации и транслитерации,
- %% но версия оригинала очевидно ошибочная
- %%
- \index{функция!qsort@\texttt{qsort}}%
- Наша версия быстрой сортировки, разумеется, не самая быстрая среди всех
- возможных, но зато одна из самых простых. В качестве делящего элемента мы
- используем серединный элемент.
- \begin{LongCodePar}
- /* qsort: сортирует v[left]...v[right] по возрастанию */
- void qsort(int v[], int left, int right)
- {
- int i, last;
- void swap(int v[], int i, int j);
- if (left >= right) /* ничего не делается, если */
- return; /* в массиве менее двух элементов */
- swap(v, left, (left + right)/2); /* делящий элемент */
- last = left; /* переносится в v[0] */
- for (i = left+1; i <= right; i++) /* деление на части */
- if (v[i] < v[left])
- 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}}%
- В нашей программе операция перестановки оформлена в виде отдельной функции
- (\verb|swap|), поскольку трижды встречается в \verb|qsort|.
- \begin{LongCodePar}
- /* swap: переставить v[i] и v[j] между собой */
- void swap(int v[], int i, int j)
- {
- int temp;
- temp = v[i];
- v[i] = v[j];
- v[j] = temp;
- }
- \end{LongCodePar}
- \noindent Стандартная библиотека имеет функцию \verb|qsort|, позволяющую
- сортировать объекты любого типа.
- \index{эффективность}%
- Рекурсивная программа не обеспечивает ни экономного расходования памяти,
- поскольку требуется где-то поддерживать стек значений, подлежащих обработке, ни
- быстродействия; но по сравнению со своим нерекурсивным эквивалентом она часто
- короче, а часто намного легче для написания и понимания. Такого рода программы
- особенно удобны для обработки рекурсивно определяемых структур данных типа
- деревьев; с хорошим примером на эту тему вы познакомитесь в
- разд.~\ref{sec:self_reference_structures}.
- \paragraph{Упражнение 4.12.} Примените идеи, которые мы использовали в
- \verb|printd| для написания рекурсивной версии функции \verb|itoa|; иначе
- говоря, преобразуйте целое число в цепочку цифр с помощью рекурсивной программы.
- \paragraph{Упражнение 4.13.} Напишите рекурсивную версию функции
- \verb|reverse(s)|, оборачивающую стринг на его же месте.
- \section{Си-препроцессор}
- \label{sec:c_preprocessor}
- \index{макро-процессор}%
- \index{препроцессор}%
- \index{управляющая!строка}%
- Некоторые возможности языка Си обеспечиваются препроцессором, который работает
- на первом шаге компиляции. Наиболее часто используются две возможности:
- \verb|#include|, включающая файл во время компиляции, и \verb|#define|,
- заменяющая одни текстовые цепочки на другие. В этом разделе обсуждаются условная
- компиляция и макроподстановка с аргументами.
- \subsection{Включение файла}
- \index{файл!включение}%
- \index{include@\texttt{{\#}include}}%
- Средство \verb|#include| позволяет, в частности, легко манипулировать наборами
- \verb|#define| и деклараций. Любая строка вида
- \begin{ShortCodeParWithCC}{\\\{\}}
- #include "\textit{имя-файла}"
- \end{ShortCodeParWithCC}
- \noindent или
- \begin{ShortCodeParWithCC}{\\\{\}}
- #include <\textit{имя-файла}>
- \end{ShortCodeParWithCC}
- \noindent заменяется содержимым файла с именем \textit{имя-файла}. Если
- \textit{имя-файла} заключено в двойные кавычки, то, как правило, файл ищется
- среди исходных файлов программы; если такового не оказалось или
- \textit{имя-файла} заключено в угловые скобки \verb|<| и \verb|>|, то поиск
- осуществляется в системных библиотеках. Включаемый файл сам может содержать в
- себе строки \verb|#include|.
- \index{файл!головной!<stdio.h>@\texttt{<stdio.h>}}%
- \index{stdio.h@\texttt{<stdio.h>}}%
- Часто исходные файлы начинаются с нескольких строк \verb|#include|, ссылающихся
- на общие инструкции \verb|#define| и \verb|extern|-декларации или прототипы
- нужных библиотечных функций из головных файлов типа \verb|<stdio.h>|. (Строго
- говоря, эти включения не обязательно являются файлами; технические детали того,
- как осуществляется доступ к головной информации, зависят от конкретной
- реализации.)
- Средство \verb|#include| -- хороший способ собрать вместе декларации большой
- программы. Он гарантирует, что все исходные файлы будут пользоваться одними и
- теми же определениями и описаниями переменных, благодаря чему предотвращаются
- особенно неприятные ошибки. Естественно, при внесении изменений во включаемый
- файл все зависимые от него файлы должны перекомпилироваться.
- \subsection{Макроподстановка}
- \index{препроцессор!макрос}%
- \index{define@\texttt{{\#}define}}%
- Определение макроподстановки имеет вид:
- \begin{ShortCodeParWithCC}{\\\{\}}
- #define \textit{имя} \textit{замещающий-текст}
- \end{ShortCodeParWithCC}
- \noindent Макроподстановка используется для простейшей замены: во всех местах,
- где встречается лексема \textit{имя}, вместо неё будет помещён
- \textit{замещающий-текст}. Имена в \verb|#define| задаются по тем же правилам,
- что и имена обычных переменных. Замещающий текст может быть произвольным. Обычно
- замещающий текст завершает строку, в которой расположено слово \verb|#define|,
- \index{define@\texttt{{\#}define}!в несколько строк}%
- но в длинных определениях его можно продолжить на следующих строках, поставив в
- конце каждой продолжаемой строки обратную наклонную черту \verb|\|. Область
- действия имени в \verb|#define| простирается от данного определения до конца
- файла. В определении макроподстановки могут фигурировать более ранние
- \verb|#define|-определения. Подстановка осуществляется только для тех имён,
- которые расположены вне текстов, заключённых в кавычки. Например, если
- \verb|YES| определено с помощью \verb|#define|‚ то никакой подстановки в
- \verb|printf("YES")| или в \verb|YESMAN| выполнено не будет.
- Любое имя можно определить с произвольным замещающим текстом.
- \index{бесконечный цикл \texttt{for(;;)}}%
- \index{for(;;) бесконечный цикл@\texttt{for(;;)} бесконечный цикл}%
- Например,
- \begin{ShortCodePar}
- #define forever for(;;) /* бесконечный цикл */
- \end{ShortCodePar}
- \noindent определяет новое слово \verb|forever| для бесконечного цикла.
- \index{макрос!с аргументами}%
- \index{define@\texttt{{\#}define}!с аргументами}%
- Макроподстановку можно определить с аргументами, вследствие чего замещающий
- текст будет варьироваться в зависимости от задаваемых параметров. Например,
- определим \verb|max| следующим образом:
- \begin{ShortCodePar}
- #define max(A, B) ((A) > (B) ? (A) : (B))
- \end{ShortCodePar}
- \noindent Хотя обращения к \verb|max| выглядят как обычные обращения к функции,
- они будут вызывать только текстовую замену. Каждый формальный параметр (в данном
- случае \verb|A| и \verb|B|) будет заменяться соответствующим ему аргументом.
- Так, строка
- \begin{ShortCodePar}
- x = max(p+q, r+s);
- \end{ShortCodePar}
- \noindent будет заменена на строку
- \begin{ShortCodePar}
- x = ((p+q) > (r+s) ? (p+q) : (r+s));
- \end{ShortCodePar}
- \noindent Поскольку аргументы допускают любой вид замены, указанное определение
- \verb|max| подходит для данных любого типа, так что не нужно писать разные
- \verb|max| для данных разных типов, как это было бы в случае задания с помощью
- функций.
- \index{побочный эффект}%
- Если вы внимательно проанализируете текстовое расширение \verb|max|, то
- обнаружите некоторые подводные камни. Выражения вычисляются дважды и, если они
- вызывают побочный эффект (из-за инкрементных операций или функций ввода-вывода),
- это может привести к нежелательным последствиям. Например,
- \begin{ShortCodePar}
- max(i++, j++) /* НЕВЕРНО */
- \end{ShortCodePar}
- \noindent вызовет увеличение \verb|i| и \verb|j| дважды. Кроме того, следует
- позаботиться о скобках, чтобы обеспечить нужный
- \index{вычисление, порядок}%
- \index{порядок!вычислений}%
- порядок вычислений. Посмотрите, что случится, если при определении
- \begin{ShortCodePar}
- #define square(x) x * x /* НЕВЕРНО */
- \end{ShortCodePar}
- \noindent воспользоваться записью типа \verb|square(z+1)|.
- Тем не менее макросредства имеют свои преимущества.
- \index{файл!головной!<stdio.h>@\texttt{<stdio.h>}}%
- \index{stdio.h@\texttt{<stdio.h>}}%
- Практическим примером их использования являются часто применяемые \verb|getchar|
- и \verb|putchar| из \verb|<stdio.h>|‚ которые реализованы с помощью макросов,
- чтобы избежать накладных расходов от вызова функции на каждую обрабатываемую
- литеру. Функции в \verb|<ctype.h>| обычно также реализуются с помощью макросов.
- \index{undef@\texttt{{\#}undef}}%
- Имена можно <<скрыть>> от препроцессора с помощью \verb|#undef|.
- \begin{ShortCodePar}
- #undef getchar
- int getchar(void) { ... }
- \end{ShortCodePar}
- \noindent Как правило, это делается, чтобы макроопределение <<перекрыть>>
- настоящей функцией с тем же именем.
- Имена формальных параметров не заменяются, если они находятся в стрингах,
- заключённых в кавычки.
- \index{препроцессор!оператор \texttt{\#}}%
- А вот комбинация в замещающем тексте: формальный параметр с предшествующим
- знаком \verb|#| -- в макрорасширении будет заменена на аргумент, заключённый в
- кавычки.
- \index{конкатенация!стрингов}%
- Такую конструкцию можно использовать для конкатенации с другими литерными
- стрингами, как‚ например‚ в следующем макроопределении, предназначенном для
- отладочной выдачи:
- \begin{ShortCodePar}
- #define dprint(expr) printf(#expr " = %g\n", expr)
- \end{ShortCodePar}
- \noindent Обращение к макроопределению
- \begin{ShortCodePar}
- dprint(x/y)
- \end{ShortCodePar}
- \noindent даст расширение
- \begin{ShortCodePar}
- printf("x/y" " = %g\n", x/y);
- \end{ShortCodePar}
- \noindent а в результате конкатенации двух соседних стрингов получим
- \begin{ShortCodePar}
- printf("x/y = %g\n", x/y);
- \end{ShortCodePar}
- \noindent Внутри фактического аргумента каждый знак \verb|"| заменяется на
- \verb|\"|, а каждая \verb|\| на \verb|\\| так, чтобы результат подстановки
- приводил к правильной литерной константе.
- \index{конкатенация!лексем}%
- \index{лексема!конкатенация}%
- \index{препроцессор!оператор \texttt{\#\#}}%
- Оператор \verb|##| препроцессором понимается как операция конкатенации; он
- позволяет в макрорасширениях конкатенировать аргументы. Если в замещающем тексте
- параметр соседствует с \verb|##|, то он заменяется на соответствующий ему
- аргумент, а оператор \verb|##| и окружающие его пробельные литеры выбрасываются.
- Например, в макроопределении \verb|paste| конкатенируются два аргумента
- \begin{ShortCodePar}
- #define paste(front, back) front ## back
- \end{ShortCodePar}
- \noindent так что \verb|paste(name, 1)| сгенерирует имя \verb|name1|.
- Правила вложенных использований оператора \verb|##| не определены; другие
- подробности, относящиеся к \verb|##|, можно найти в
- приложении~\ref{apx:ref_manual}.
- \paragraph{Упражнение 4.14.} Определите \verb|swap(t,x,y)| в виде макроса,
- который осуществляет обмен значениями указанного типа \verb|t| между аргументами
- \verb|x| и \verb|y|. (Примените блочную структуру.)
- \subsection{Условная компиляция}
- \index{условная компиляция}%
- Самим ходом препроцессирования можно управлять с помощью условных инструкций.
- Они представляют собой средство для выборочного включения того или иного текста
- программы в зависимости от значения условия, вычисляемого во время компиляции.
- Вычисляется
- \index{выражение!константное}%
- \index{константное выражение}%
- \index{if@\texttt{{\#}if}}%
- константное целое выражение, заданное в строке \verb|#if|.
- \index{оператор!sizeof@\texttt{sizeof}}%
- Это выражение не должно содержать ни одного оператора \verb|sizeof| или
- приведения к типу и ни одной \verb|enum|-константы. Если оно имеет ненулевое
- значение, то будут включены все последующие строки вплоть до
- \index{endif@\texttt{{\#}endif}}%
- \verb|#endif|,
- \index{elif@\texttt{{\#}elif}}%
- или \verb|#elif|,
- \index{else a@\texttt{{\#}else}}%
- или \verb|#else|. (Инструкция препроцессора \verb|#elif|
- похожа на \verb|else if|.)
- \index{препроцессор!оператор \texttt{defined}}%
- \index{defined@\texttt{defined}}%
- Выражение \verb|defined(|\textit{\Verb|имя|}\verb|)| в \verb|#if| есть $1$, если
- \textit{имя} было определено, и $0$ в противном случае.
- Например, чтобы застраховаться от повторного включения головного файла
- \verb|hdr.h|, его можно оформить следующим образом:
- \begin{ShortCodePar}
- #if !defined(HDR)
- #define HDR
- /* здесь содержимое hdr.h */
- #endif
- \end{ShortCodePar}
- \noindent При первом включении файла \verb|hdr.h| будет определено имя
- \verb|HDR|, а при последующих включениях препроцессор обнаружит, что имя
- \verb|HDR| уже определено и перепрыгнет сразу на \verb|#endif|. Этот приём может
- оказаться полезным, когда нужно избежать многократного включения одного и того
- же файла. Если им пользоваться систематически, то в результате каждый головной
- файл будет сам включать головные файлы, от которых он зависит, освободив от
- этого занятия пользователя.
- Вот пример цепочки проверок имени \verb|SYSTEM|, позволяющей выбрать нужный файл
- для включения:
- \begin{LongCodePar}
- #if SYSTEM == SYSV
- #define HDR "sysv.h"
- #elif SYSTEM == BCD
- #define HDR "bcd.h"
- #elif SYSTEM == MSDOS
- #define HDR "msdos.h"
- #else
- #define HDR "default.h"
- #endif
- #include HDR
- \end{LongCodePar}
- \noindent%
- \index{ifdef@\texttt{{\#}ifdef}}%
- \index{ifndef@\texttt{{\#}ifndef}}%
- Инструкции \verb|#ifdef| и \verb|#ifndef| специально предназначены для
- проверки того, определено или не определено заданное в них имя. И следовательно,
- первый пример, приведённый выше для иллюстрации \verb|#if|, можно записать и в
- таком виде:
- \begin{ShortCodePar}
- #ifndef HDR
- #define HDR
- /* здесь содержимое hdr.h */
- #endif
- \end{ShortCodePar}
|