| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043 |
- \chapter{Обзор языка}
- \label{chapt:tutorial_intro}
- Начнём с быстрого ознакомления с языком Си. Наша цель -- показать на реальных
- программах существенные элементы языка, не вдаваясь в мелкие детали, формальные
- правила и исключения из них. Поэтому мы не стремимся к полноте и даже точности
- (заботясь, однако, о корректности примеров). Нам бы хотелось как можно скорее
- подвести вас к моменту, когда вы сможете писать полезные программы, и, чтобы
- сделать это, мы должны сконцентрировать внимание на основах: переменных и
- константах, арифметике, управлении последовательностью вычислений, функциях и
- простейшем вводе-выводе. В настоящей главе мы умышленно не затрагиваем тех
- средств языка, которые важны при написании больших программ. Сюда входят
- указатели, структуры, большая часть богатого набора операторов, несколько
- управляющих инструкций и стандартная библиотека.
- Такой подход имеет свои недостатки. Наиболее заметный из них связан с тем, что
- мы не даём здесь законченного изложения свойств языка, и эта лаконичность может
- привести к неправильному восприятию некоторых положений. В силу ограниченного
- характера подачи материала в примерах не используется вся мощь языка, и потому
- они не столь кратки и элегантны, как могли бы быть. Мы попытались по возможности
- смягчить эти эффекты, но считаем необходимым предупредить о них. Другой
- недостаток в том, что в последующих главах какие-то моменты нам придётся
- повторить. Мы надеемся, что польза от повторений превысит вызываемое ими
- раздражение.
- В любом случае опытный программист, видимо, сможет материал данной главы
- проэкстраполировать на свои программистские нужды. Новичкам же рекомендуем её
- чтение дополнить написанием своих маленьких программ. И те и другие наши
- читатели могут рассматривать эту главу как <<каркас>>, на который далее, начиная
- с гл.~\ref{chapt:types-operators-expressions}, будут <<навешиваться>> элементы
- языка.
- \section{Начнём, пожалуй}
- Единственный способ выучить новый язык программирования -- это писать на нём
- программы. При изучении любого языка первой, как правило, предлагают написать
- приблизительно следующую программу:
- \begin{ShortCodeParWithCC}{\\\{\}}
- \textit{Напечатайте слова}
- здравствуй, мир
- \end{ShortCodeParWithCC}
- \noindent%
- \index{компиляция!Си-программ}%
- Вот первое препятствие, и чтобы его преодолеть, вы должны суметь где-то создать
- текст программы, успешно его скомпилировать, загрузить, пустить на счёт и
- разобраться, куда будет отправлен результат. Как только вы овладеете этим, всё
- остальное окажется относительно простым.
- Си-программа, печатающая <<здравствуй, мир>>, выглядит так:
- \begin{ShortCodePar}
- #include <stdio.h>
- main()
- {
- printf ("здравствуй, мир\n");
- }
- \end{ShortCodePar}
- Как запустить эту программу, зависит от системы, которую вы используете. Так, в
- операционной системе UNIX необходимо сформировать исходную программу в файле с
- именем, заканчивающимся символами <<.с>>, например, в файле \verb|hello.c|,
- \index{cc@\texttt{cc}}%
- который затем требуется скомпилировать с помощью команды
- \begin{ShortCodePar}
- cc hello.c
- \end{ShortCodePar}
- \noindent%
- \index{a.out@\texttt{a.out}}%
- Если вы всё сделали правильно -- не пропустили где-либо знака и не
- допустили орфографических ошибок, то компиляция пройдёт <<молча>>, и вы получите
- файл, готовый к исполнению и названный \verb|a.out|. Если вы теперь запустите
- этот файл на счёт командой
- \begin{ShortCodePar}
- a.out
- \end{ShortCodePar}
- \noindent программа напечатает
- \begin{ShortCodePar}
- здравствуй, мир
- \end{ShortCodePar}
- \noindent В других системах правила запуска программы на выполнение могут быть
- иными; чтобы узнать о них, поговорите со специалистами.
- Теперь поясним некоторые моменты, касающиеся самой программы. Программа на Си,
- каких бы размеров она ни была, состоит из \emph{функций} и
- \emph{переменных}. Функции содержат \emph{инструкции}, описывающие
- вычисления, которые необходимо выполнить, а переменные хранят значения,
- используемые в процессе этих вычислений.
- \index{функция}%
- \index{функция!main@\texttt{main}}%
- \index{main@\texttt{main}}%
- Функции в Си похожи на подпрограммы и функции Фортрана и на процедуры и функции
- Паскаля. Приведённая программа -- это функция с именем \verb|main|. Обычно вы
- вольны придумывать любые имена для своих функций, но <<\verb|main|>> -- особое
- имя: любая программа начинает свои вычисления с первой инструкции функции
- \verb|main|.
- Обычно \verb|main| для выполнения своей работы пользуется услугами других
- функций; одни из них пишутся самим программистом, а другие берутся им готовыми
- из имеющихся в его распоряжении библиотек.
- \index{файл!головной!<stdio.h>@\texttt{<stdio.h>}}%
- \index{stdio.h@\texttt{<stdio.h>}}%
- Первая строка программы:
- \begin{ShortCodePar}
- #include <stdio.h>
- \end{ShortCodePar}
- \noindent сообщает компилятору, что он должен включить информацию о стандартной
- библиотеке ввода-вывода. Эта строка встречается в начале многих исходных файлов
- Си-программ. Стандартная библиотека описана в гл.~\ref{chapt:input-output} и
- приложении~\ref{apx:stdlib}.
- Один из способов передачи данных между функциями состоит в том, что функция при
- обращении к другой функции передаёт ей список значений, называемых
- \emph{аргументами}. Этот список обрамляется скобками и помещается после имени
- функции. В нашем примере \verb|main| определена как функция, которая не ждёт
- никаких аргументов. что отмечено пустым списком \verb|()|.
- \FloatBarrier
- \begin{center}
- \begin{table}[H]
- \hspace{-1\tabcolsep}\begin{tabular*}{\linewidth+2\tabcolsep}% умышленный overfull на \tabcolsep
- {p{.5\linewidth-\tabcolsep}p{.5\linewidth-\tabcolsep}}
- \multicolumn{2}{c}{ПЕРВАЯ ПРОГРАММА НА СИ}\\
- \verb|#include <stdio.h>|
- &\textit{Включение информации о стандартной библиотеке.}\\
- \verb|main()| &
- \textit{Определение функции с именем }\Verb|main|\textit{, не получающей никаких
- аргументов.}\\
- \verb|{|
- &\multirow{2}={\begin{minipage}{\linewidth}\textit{Инструкции
- }\Verb|main|\textit{ заключаются в фигурные скобки.}\end{minipage}}\\
- &\\
- \verb| printf("здравствуй,мир\n");|
- &\multirow{4}={\begin{minipage}{\linewidth}\textit{Функция }\Verb|main|\textit{
- обращается к библиотечной функции }\Verb|printf|\textit{ для печати заданной
- последовательности литер; }\Verb|\textbackslash n|\textit{ -- литера
- новая-строка.}\end{minipage}}\\
- &\\
- \verb|}|&\\
- &\\
- \end{tabular*}
- \end{table}
- \end{center}
- \index{фигурные скобки}%
- Инструкции функции заключаются в фигурные скобки \verb|{ }|. Функция
- \verb|main| содержит только одну инструкцию:
- \begin{ShortCodePar}
- printf ("здравствуй, мир\n");
- \end{ShortCodePar}
- \noindent Функция вызывается по имени, после которого, в скобках, указывается
- список аргументов. Таким образом, приведённая строка -- это вызов функции
- \verb|printf| с аргументом \verb|"здравствуй, мир\n"|. \verb|printf| --
- \index{библиотечная функция}%
- библиотечная функция, которая в данном случае напечатает последовательность
- литер, заключённую в двойные кавычки.
- \index{литера!двойная кавычка \texttt{\char`\"}}%
- \index{константа!стринговая}%
- Цепочка литер в двойных кавычках типа \verb|"здравствуй, мир\n"| называется
- \emph{стрингом литер или стринговой константой}. Какое-то время в качестве
- аргументов для \verb|printf| и других функций мы будем использовать только
- стринги литер.
- \index{литера!новая-строка \texttt{\textbackslash n}}%
- \index{n@\texttt{\textbackslash n}}%
- В Си комбинация \verb|\n| внутри стринга обозначает \emph{литеру новая-строка},
- которая при печати вызывает переход к левому краю следующей строки. Если вы
- удалите \verb|\n| (стоит поэкспериментировать), то обнаружите, что, закончив
- печать, машина не переходит на новую строку. Литеру новая-строка в текстовый
- аргумент \verb|printf| следует включать явным образом. Если вы попробуете
- выполнить, например,
- \begin{ShortCodePar}
- printf ("здравствуй, мир
- ");
- \end{ShortCodePar}
- \noindent компилятор выдаст сообщение об ошибке.
-
- Литера новая-строка никогда не вставляется автоматически, так что одну строку
- можно напечатать по шагам с помощью нескольких обращений к \verb|printf|. Нашу
- первую программу можно написать и так:
- \begin{ShortCodePar}
- #include <stdio.h>
- main()
- {
- printf ("здравствуй, ");
- printf ("мир");
- printf ("\n");
- }
- \end{ShortCodePar}
- \noindent В результате её выполнения будет напечатана та же строка, что и
- раньше.
- \index{эскейп-последовательность}%
- Заметим, что \verb|\n| обозначает только одну литеру. Такие особые комбинации
- литер, начинающиеся с обратной наклонной черты, как \verb|\n|, и называемые
- \emph{эскейп-последовательностями}, широко применяются для обозначения трудно
- представимых или невидимых литер.
- \index{литера!обратная наклонная черта \texttt{\textbackslash\textbackslash}}%
- \index{b (backspace)@\texttt{\textbackslash b} (backspace)}%
- Среди прочих в Си имеются литеры \verb|\t|, \verb|\b|, \verb|\"|, \verb|\\|,
- обозначающие соответственно табуляцию, возврат на одну литеру назад (<<забой>>
- последней литеры), двойную кавычку, саму наклонную черту. Полный список таких
- литер представлен в разд.~\ref{sec:constants}.
- \paragraph{Упражнение 1.1.} Выполните программу, печатающую ``здравствуй, мир'',
- в вашей системе. Поэкспериментируйте, удаляя некоторые части программы, и
- посмотрите, какие сообщения об ошибках вы получите.
- \paragraph{Упражнение 1.2.} Выясните, что произойдёт, если в стринговую
- константу аргумента \verb|printf| вставить \textbackslash\textit{c}, где
- \textit{с} -- литера, не входящая в перечисленный выше список.
- \section{Переменные и арифметические выражения}
- \index{программа!преобразования температур}%
- Следующая программа выполняет вычисления по формуле
- ${C^{\circ} =(5/9)(F^{\circ} -32)}$ и печатает приведённую ниже таблицу
- соответствия температур по Фаренгейту и по Цельсию:
- \begin{LongCodePar}
- 0 -17
- 20 -6
- 40 4
- 60 15
- 80 26
- 100 37
- 120 48
- 140 60
- 160 71
- 180 82
- 200 93
- 220 104
- 240 115
- 260 126
- 280 137
- 300 148
- \end{LongCodePar}
- \noindent Как и предыдущая, эта программа состоит из определения
- одной-единственной функции \verb|main|. Она длиннее программы, печатающей
- \verb|"здравствуй, мир"|, но не сложная. На ней мы продемонстрируем несколько
- новых возможностей, включая комментарий, описания, переменные, арифметические
- выражения, циклы и форматный вывод.
- \begin{LongCodePar}
- #include <stdio.h>
- /* печать таблицы температур по Фаренгейту
- и Цельсию для fahr = 0, 20, ..., 300 */
- main()
- {
- int fahr, celsius;
- int lower, upper, step;
-
- lower = 0; /* нижняя граница табл. температур */
- upper = 300; /* верхняя граница */
- step = 20; /* шаг */
-
- fahr = lower;
- while (fahr <= upper) {
- celsius = 5 * (fahr-32) / 9;
- printf("%d\t%d\n", fahr, celsius);
- fahr = fahr + step;
- }
- }
- \end{LongCodePar}
- \noindent%
- \index{комментарий}%
- Две строки:
- \begin{ShortCodePar}
- /* печать таблицы температур по Фаренгейту
- и Цельсию для fahr = 0, 20, ..., 300 */
- \end{ShortCodePar}
- \noindent являются \emph{комментарием}, который в данном случае кратко
- объясняет, что делает программа. Любые литеры, помещённые между \verb|/*| и
- \verb|*/|, игнорируются компилятором, и ими можно свободно пользоваться, чтобы
- сделать программу более понятной. Комментарий можно располагать в любом месте,
- где могут стоять литеры пробела, табуляции или литера новая-строка.
- В Си любая переменная должна быть описана раньше, чем она будет использована;
- обычно все переменные описываются в начале функции перед первой исполняемой
- инструкцией.
- \index{декларация}%
- В \emph{декларации (описании)} объявляются свойства переменных. Она состоит из
- названия типа и списка переменных, как, например
- \begin{ShortCodePar}
- int fahr, celsius;
- int lower, upper, step;
- \end{ShortCodePar}
- Тип \verb|int|, означает, что значения перечисленных переменных есть целые, в
- отличие от него тип \verb|float| указывает на значения с плавающей точкой, т.е.
- на числа, которые могут иметь дробную часть. Диапазоны значений обоих типов
- зависят от машины.
- \index{числа!размер}%
- Числа типа \verb|int| бывают как 16-разрядные (они лежат в диапазоне от $-32768$
- до $32767$), так и 32-разрядные Числа типа \verb|float| обычно представляются
- 32-разрядными словами, имеющими по крайней мере 6 десятичных значащих цифр, и
- лежат приблизительно в диапазоне от $10^{-38}$ до $10^{+38}$.
- \index{типы!базовые}%
- \index{int@\texttt{int}!тип}%
- Помимо \verb|int| и \verb|float| Си допускает ещё несколько базовых типов для
- данных, это:
- \begin{LongRtAlTab2cols}{\codeIndent}{0pt}{\Verb|double|}
- \verb|char|%
- \index{char@\texttt{char}!тип}%
- &литера -- единичный байт\\
- \verb|short|%
- \index{short@\texttt{short}}%
- &короткое целое\\
- \verb|long|%
- \index{long@\texttt{long}!тип}%
- &длинное целое\\
- \verb|double|%
- \index{double@\texttt{double}!тип}%
- &плавающее с двойной точностью
- \end{LongRtAlTab2cols}
- Размеры объектов указанных типов также зависят от машины.
- \index{выводимые типы}%
- \index{типы!выводимые}%
- Из базовых типов можно создавать: \emph{массивы}, \emph{структуры} и
- \emph{объединения}, \emph{указатели} на объекты базовых типов и функции,
- возвращающие в качестве результата значения этих типов. Обо всём этом мы
- расскажем позже.
- \index{оператор!присваивания \texttt{=}}%
- Вычисления в программе преобразования температур начинаются с \emph{инструкций
- присваивания}:
- \begin{ShortCodePar}
- lower = 0;
- upper = 300;
- step = 20;
- fahr = lower;
- \end{ShortCodePar}
- \noindent которые устанавливают указанным в них переменным их начальные
- значения.
- \index{инструкция!окончание}%
- \index{точка с запятой}%
- Любая инструкция заканчивается точкой с запятой.
- Все строки таблицы вычисляются одним и тем же способом, поэтому мы воспользуемся
- циклом, повторяющим это вычисление для каждой строки.
- \index{while@\texttt{while}!инструкция}%
- Необходимые действия выполнит цикл \verb|while|:
- \begin{ShortCodePar}
- while (fahr <= upper) {
- ...
- }
- \end{ShortCodePar}
- \noindent Он работает следующим образом. Проверяется условие в скобках. Если оно
- истинно (значение \verb|fahr| меньше или равно значению \verb|upper|), то тело
- цикла (три инструкции, заключённые в фигурные скобки) выполняется. Затем опять
- проверяется условие и, если оно истинно, то тело цикла выполняется снова. Когда
- условие становится ложным (\verb|fahr| превысило \verb|upper|), цикл
- завершается, и вычисления продолжаются с инструкции, которая следует за циклом.
- Поскольку никаких инструкций за циклом нет, программа завершает работу.
- \index{фигурные скобки}%
- Телом цикла \verb|while| может быть одна или несколько инструкций, заключённых в
- фигурные скобки, как в программе преобразования температур, или
- одна-единственная инструкция без скобок, как в цикле
- \begin{ShortCodePar}
- while (i < j)
- i = 2 * i;
- \end{ShortCodePar}
- \noindent%
- \index{отступы в тексте программы}%
- \index{программа!формат}%
- \index{программа!читаемость}%
- И в том и в другом случае инструкции, находящиеся под управлением
- \verb|while|, мы будем записывать со сдвигом, равным одной позиции табуляции,
- которая в программе указывается четырьмя пробелами; благодаря этому будут ясно
- видны инструкции, расположенные внутри цикла. Отступы подчёркивают логическую
- структуру программы. Си-компилятор не обращает внимания на внешнее оформление
- программы, но наличие в нужных местах отступов и пробелов существенно влияют на
- то, насколько легко она будет восприниматься при её просмотре. Чтобы лучше была
- видна логическая структура выражения, мы рекомендуем писать только по одной
- инструкции на каждой строке и обрамлять пробелами знаки операций.
- \index{расположение фигурных скобок}%
- \index{фигурные скобки}%
- \index{фигурные скобки!расположение}%
- Положение скобок менее важно, хотя существуют различные точки зрения на этот
- счёт. Мы остановились на одном из нескольких распространённых стилей их
- применения. Выберите тот, который больше всего вам импонирует, и точно ему
- следуйте.
- Б\'{o}льшая часть вычислений выполняется в теле цикла. Температура переводится в
- шкалу Цельсия и присваивается переменной \verb|celsius| посредством инструкции
- \begin{ShortCodePar}
- celsius = 5 * (fahr-32) / 9;
- \end{ShortCodePar}
- \noindent%
- \index{деление целых}%
- \index{оператор!деления \texttt{\slash}}%
- Мы сначала умножаем на $5$ и затем делим на $9$, а не сразу умножаем на $5/9$.
- \index{обрезание!при делении}%
- Это связано с тем, что в Си, как и во многих других языках, деление целых
- сопровождается \emph{обрезанием}, т.е. отбрасыванием дробной части. Так как $5$
- и $9$ -- целые, обрезание $5/9$ дало бы нуль, и на месте температур по Цельсию
- были бы напечатаны нули.
- Этот пример ещё немного прибавил нам знаний о том, как работает \verb|printf|.
- \verb|printf| -- универсальная функция форматного ввода-вывода, которая будет
- подробно описана в гл.~\ref{chapt:input-output}, Её первый аргумент -- стринг,
- в котором каждый знак \verb|%| соответствует одному из последующих её аргументов
- (второму, третьему\ldots), а информация, расположенная за знаком \verb|%|,
- указывает на вид, в котором каждый из этих аргументов выводится. Например,
- \verb|%d| специфицирует выдачу аргумента в виде целого числа, и инструкция
- \begin{ShortCodePar}
- printf("%d\t%d\n", fahr, celsius);
- \end{ShortCodePar}
- \noindent печатает целое \verb|fahr|, выполняет табуляцию и печатает целое
- \verb|celsius|.
- В функции \verb|printf| каждому спецификатору первого аргумента (конструкции,
- начинающейся с \verb|%|) соответствует второй аргумент, третий аргумент и т.д.
- Спецификаторы и соответствующие им аргументы должны быть согласованы по
- количеству и типам: в противном случае напечатано будет не то, что нужно.
- Кстати, \verb|printf| не является частью языка Си, и вообще нет никаких
- специальных конструкций языка, определяющих ввод-вывод. Функция \verb|printf|
- -- лишь полезная функция стандартной библиотеки, которая обычно доступна для
- Си-программ. Поведение функции \verb|printf|, однако, оговорено стандартом ANSI
- и её свойства должны быть одинаковыми во всех Си-системах, удовлетворяющих
- требованиям стандарта.
- Желая сконцентрировать ваше внимание на самом Си, мы не будем много говорить о
- вводе-выводе до гл.~\ref{chapt:input-output}. В частности, до этой главы мы
- отложим разговор о форматном вводе. Если вам потребуется ввести числа, советуем
- прочитать в разд.~\ref{sec:formatted-input} то, что касается функции
- \verb|scanf|. Эта функция аналогична \verb|printf| с той лишь разницей, что она
- вводит, а не выводит данные.
- Существуют ещё две проблемы связанные с программой преобразования температур.
- Одна из них (более простая) состоит в том, что выводимый результат выглядит
- несколько неряшливо, поскольку числа не выровнены по правой позиции колонок. Это
- легко исправить, добавив в каждый из спецификаторов \verb|%d| формата указание о
- ширине поля; при этом программа будет печатать числа, прижимая их к правому краю
- указанных полей. Например, мы можем написать
- \begin{ShortCodePar}
- printf("%3d %6d\n", fahr, celsius);
- \end{ShortCodePar}
- \noindent чтобы в каждой строке первое число печатать в поле из трёх позиций, а
- второе -- из шести. В результате будет напечатано:
- \begin{ShortCodePar}
- 0 -17
- 20 -6
- 40 4
- 60 15
- 80 28
- 100 37
- ...
- \end{ShortCodePar}
- \index{программа!преобразования температур|(}%
- Вторая, более серьёзная проблема связана с тем, что мы пользуемся целочисленной
- арифметикой и поэтому не совсем точно вычисляем температуры по шкале Цельсия.
- Например, $0^{\circ}F$ на самом деле (с точностью до десятой) равно
- $-17.8^{\circ}C$‚ а не $-17$. Чтобы получить более точные значения температур,
- нам надо пользоваться не целочисленной, а плавающей арифметикой. Это потребует
- некоторых изменений в программе.
- \begin{LongCodePar}
- #include <stdio.h>
- /* печать таблицы температур по Фаренгейту и Цельсию для
- fahr = 0, 20, ..., 300; вариант с плавающей точкой */
- main()
- {
- float fahr, celsius;
- int lower, upper, step;
-
- lower = 0; /* нижняя граница табл. температур */
- upper = 300; /* верхняя граница */
- step = 20; /* шаг */
-
- fahr = lower;
- while (fahr <= upper) {
- celsius = (5.0/9.0) * (fahr-32.0);
- printf("%3.0f %6.1f\n", fahr, celsius);
- fahr = fahr + step;
- }
- }
- \end{LongCodePar}
- Программа мало изменилась. Она отличается от предыдущей лишь тем, что
- \verb|fahr| и \verb|celsius| объявлены как \verb|float|, а формула
- преобразования написана в более естественном виде. В предыдущем варианте нельзя
- было писать \verb|5/9|, так как целочисленное деление в результате обрезания
- дало бы нуль.
- \index{константа!с плавающей точкой}%
- Десятичная точка в константе указывает, что последняя рассматривается как число
- с плавающей точкой, и \verb|5.0/9.0|‚ таким образом, есть частное от деления
- двух значений с плавающей точкой, которое не предполагает отбрасывания дробной
- части. В том случае, когда арифметическая операция имеет целые операнды, она
- выполняется по правилам целочисленной арифметики.
- \index{преобразование!целое -- плавающее}%
- Если же один операнд плавающий, а другой -- целый, то перед тем, как операция
- будет выполнена, последний будет преобразован в плавающий. Если бы мы написали
- \verb|fahr-32|, то $32$ автоматически было бы преобразовано в число с плавающей
- точкой. Тем не менее при записи констант с плавающей точкой мы всегда используем
- десятичную точку, причём даже в тех случаях, когда константы на самом деле имеют
- целые значения. Это делается для того, чтобы обратить внимание читающего
- программу на их природу.
- Более подробно правила, определяющие, в каких случаях целые переводятся в
- плавающие, рассматриваются в гл.~\ref{chapt:types-operators-expressions}. А
- сейчас заметим, что присваивание
- \begin{ShortCodePar}
- fahr = lower;
- \end{ShortCodePar}
- \noindent и проверка
- \begin{ShortCodePar}
- while (fahr <= upper)
- \end{ShortCodePar}
- \noindent работают естественным образом, т.е. перед выполнением операции
- значение \verb|int| приводится к \verb|float|.
- Спецификация \verb|%3.0f| в \verb|printf| определяет печать числа с плавающей
- точкой (в данном случае числа \verb|fahr|) в поле шириной не более трёх позиций
- без десятичной точки и дробной части. Спецификация \verb|%6.1f| описывает печать
- другого числа (\verb|celsius|) в поле из шести позиций с одной цифрой после
- десятичной точки. Напечатано будет следующее:
- \begin{ShortCodePar}
- 0 -17.8
- 20 -6.7
- 40 4.4
- ...
- \end{ShortCodePar}
- \noindent Ширину и точность можно не задавать: \verb|%6f| означает, что число
- будет занимать не более шести позиций; \verb|%.2f| -- число имеет две цифры
- после десятичной точки, но ширина не ограничена; \verb|%f| просто указывает на
- печать числа с плавающей точкой.
- \begin{LongRtAlTab2cols}{2\parindent-\codeIndent}{0pt}{\Verb|\%6.2f|}
- \verb|%d| &печать десятичного целого \\
- \verb|%6d| &печать десятичного целого в поле из 6 позиций \\
- \verb|%f| &печать числа с плавающей точкой \\
- \verb|%6f| &печать числа с плавающей точкой в поле из 6 позиций\\
- \verb|%.2f| &печать числа с плавающей точкой с 2 цифрами после
- десятичной точки \\
- \verb|%6.2f|&печать числа с плавающей точкой в поле из 6 позиций и 2 цифрами
- после десятичной точки
- \end{LongRtAlTab2cols}
- \noindent Кроме того, \verb|printf| допускает также следующие спецификаторы:
- \verb|%o| для восьмеричного числа, \verb|%x| для шестнадцатеричного числа,
- \verb|%c| для литеры, \verb|%s| для стринга литер и \verb|%%| для самого \%.
- %
- % исправлена орфографическая ошибка ``шестнадцатиричного''
- %
- \paragraph{Упражнение 1.3.} Усовершенствуйте программу преобразования температур
- таким образом, чтобы над таблицей она печатала заголовок.
- \paragraph{Упражнение 1.4.} Напишите программу, которая будет печатать таблицу
- соответствия температур по Цельсию температурам по Фаренгейту.
- \section{Инструкция \texorpdfstring{\protect\Verb|for|}{for}}
- \index{for@\texttt{for}!инструкция}%
- Существует много разных способов для написания одной и той же программы.
- Видоизменим нашу программу преобразования температур:
- \begin{LongCodePar}
- #include <stdio.h>
- /* печать таблицы температур по Фаренгейту и Цельсию */
- main()
- {
- int fahr;
- for (fahr = 0; fahr <= 300; fahr = fahr + 20)
- printf("%3d %6.1f\n", fahr, (5.0/9.0)*(fahr-32));
- }
- \end{LongCodePar}
- \noindent Эта программа печатает тот же результат, но выглядит она, несомненно,
- по-другому. Главное отличие в отсутствии большинства переменных. Осталась только
- переменная \verb|fahr|, которую мы описали как \verb|int|. Нижняя и верхняя
- границы и шаг присутствуют в виде констант в инструкции \verb|for| -- новой для
- нас конструкции, а выражение, вычисляющее температуру по Цельсию, теперь задано
- третьим аргументом функции \verb|printf|, а не в отдельной инструкции
- присваивания.
- Это последнее изменение является примером применения общего правила: в любом
- контексте, где возможно использование значения переменной какого-то типа, можно
- использовать более сложное выражение того же типа. Так, на месте третьего
- аргумента функции \verb|printf| согласно спецификатору \verb|%6.1f| должно быть
- значение с плавающей точкой, следовательно, здесь может быть любое выражение
- этого типа.
- Инструкция \verb|for| описывает цикл, который является обобщением цикла
- \verb|while|. Если вы сравните его с ранее написанным \verb|while|, то вам
- станет ясно, как он работает. Внутри скобок имеются три выражения, разделяемых
- точкой с запятой. Первое выражение -- инициализация
- \begin{ShortCodePar}
- fahr = 0
- \end{ShortCodePar}
- \noindent -- выполняется один раз перед тем, как войти в цикл. Второе --
- проверка условия продолжения цикла
- \begin{ShortCodePar}
- fahr <= 300
- \end{ShortCodePar}
- \noindent Условие вычисляется, и, если оно истинно, тело цикла (в нашем случае
- это одно обращение к \verb|printf|) выполняется. Затем осуществляется приращение
- шага:
- \begin{ShortCodePar}
- fahr = fahr + 20
- \end{ShortCodePar}
- \noindent и условие вычисляется снова. Цикл заканчивается, когда условие
- становится ложным. Как и в случае с \verb|while|‚ тело \verb|for|-цикла может
- состоять из одной инструкции или из нескольких, заключённых в фигурные скобки.
- На месте этих трёх выражений (инициализации, условия и приращения шага) могут
- стоять произвольные выражения.
- \index{for\texttt{for}!вместо \texttt{while}}%
- \index{while@\texttt{while}!вместо \texttt{for}}%
- Выбор между \verb|while| и \verb|for| определяется соображениями ясности
- программы. Цикл \verb|for| более удобен в тех случаях, когда инициализация и
- приращение шага логически связаны друг с другом общей переменной и выражаются
- единичными инструкциями, поскольку он компактнее цикла \verb|while|, а его
- управляющие части сосредоточены в одном месте.
- \index{программа!преобразования температур|)}%
- \paragraph{Упражнение 1.5.} Измените программу преобразования температур так,
- чтобы она печатала таблицу в обратном порядке, т.е. от $300$ до $0$.
- \section{Именованные константы}
- Прежде чем мы закончим рассмотрение программы преобразования температур,
- выскажем ещё одно соображение.
- \index{магические числа}%
- Очень плохо, когда по программе рассеяны <<магические числа>> типа $300$, $20$.
- Для того кто будет читать программу, в них нет и намёка на то, что они собой
- представляют. Кроме того, их трудно заменить на другие каким-то систематическим
- способом. Одна из возможностей справиться с такими числами -- дать им
- осмысленные имена.
- \index{define@\texttt{{\#}define}}%
- Строка \verb|#define| определяет \emph{символьное имя}, или
- \emph{именованную константу}, для заданного стринга литер:
- \begin{ShortCodeParWithCC}{\\\{\}}
- #define \textit{имя} \textit{подставляемый-текст}
- \end{ShortCodeParWithCC}
- \noindent С этого момента при любом появлении имени (если только оно встречается
- не в тексте, заключённом в кавычки, и не является частью определения другого
- имени) оно будет заменяться на соответствующий ему \textit{подставляемый-текст}.
- Имя имеет тот же вид, что и переменная: последовательность букв и цифр,
- начинающаяся с буквы. \textit{Подставляемый-текст} может быть любой
- последовательностью литер, среди которых могут встречаться не только цифры.
- \begin{LongCodePar}
- #include <stdio.h>
- #define LOWER 0 /* нижняя граница таблицы */
- #define UPPER 300 /* верхняя граница */
- #define STEP 20 /* размер шага */
- /* печать таблицы температур по Фаренгейту и Цельсию */
- main()
- {
- int fahr;
- for (fahr = LOWER; fahr <= UPPER; fahr = fahr + STEP)
- printf("%3d %6.1f\n", fahr, (5.0/9.0)*(fahr-32));
- }
- \end{LongCodePar}
- \noindent Величины \verb|LOWER|, \verb|UPPER| и \verb|STEP| -- именованные
- константы, а не переменные. Поэтому для них нет описаний. По общепринятому
- соглашению имена именованных констант набираются заглавными буквами, чтобы они
- отличались от обычных переменных, набираемых строчными.
- \index{точка с запятой}%
- Заметим, что в конце \verb|#define|-строки точка с запятой не ставится.
- \section{Ввод-вывод литер}
- \index{литера!ввод-вывод}%
- \index{ввод!с клавиатуры}%
- Теперь мы намерены рассмотреть семейство программ по текстовой обработке. Вы
- обнаружите, что многие реальные программы являются просто более развитыми
- версиями описываемых здесь программ.
- Стандартная библиотека поддерживает очень простую модель
- \index{ввод-вывод!литер}%
- ввода-вывода.
- \index{поток!текстовый}%
- \index{текстовый поток}%
- Текстовый ввод-вывод вне зависимости от того, откуда он исходит или куда
- направляется, имеет дело с потоком литер.
- \index{литера!новая-строка \texttt{\textbackslash n}}%
- \index{n@\texttt{\textbackslash n}}%
- \emph{Текстовый поток} -- это последовательность литер, разбитая на строки,
- каждая из которых содержит нуль или более литер и завершается литерой
- новая-строка. Обязанность следить за тем, чтобы любой поток ввода-вывода отвечал
- этой модели, возложена на библиотеку: программист, пользуясь библиотекой, не
- должен заботиться о том, в каком виде строки представляются вне программы.
- Стандартная библиотека включает несколько функций для чтения и записи одной
- литеры. Простейшие из них --
- \index{библиотечная функция!getchar@\texttt{getchar}}%
- \verb|getchar| и
- \index{библиотечная функция!putchar@\texttt{putchar}}%
- \verb|putchar|. За одно обращение к \verb|getchar| читается \emph{следующая
- литера ввода} из текстового потока, которая и выдаётся в качестве результата.
- Так, после выполнения
- \begin{ShortCodePar}
- c = getchar();
- \end{ShortCodePar}
- \noindent переменная \verb|c| содержит следующую литеру ввода. Обычно
- \index{ввод-вывод!с терминала}%
- литеры поступают с клавиатуры. Ввод из файлов рассматривается в
- гл.~\ref{chapt:input-output}.
- Обращение к \verb|putchar| приводит к печати одной литеры. Так,
- \begin{ShortCodePar}
- putchar(c);
- \end{ShortCodePar}
- \noindent напечатает содержимое целой переменной \verb|c| в виде
- \index{вывод!на экран}%
- литеры (обычно на экране). Вызовы \verb|putchar| и \verb|printf| могут
- произвольным образом перемежаться. Вывод будет формироваться в том же порядке, в
- каком осуществляются обращения к этим функциям.
- \subsection{Копирование файла}
- \index{программа!копирования файлов|(}%
- При наличии функций \verb|getchar| и \verb|putchar|, ничего больше не зная о
- вводе-выводе, можно написать удивительно много полезных программ. Простейший
- пример -- это программа, копирующая по одной литере с входного потока в
- выходной поток
- \begin{ShortCodeParWithCC}{\\\{\}}
- \textit{чтение литеры}
- while (\textit{литера не есть признак-конца-файла})
- \textit{вывод только что прочитанной литеры}
- \textit{чтение литеры}
- \end{ShortCodeParWithCC}
- \noindent Оформляя её в виде программы на Си, получим
- \begin{LongCodePar}
- #include <stdio.h>
- /* копирование ввода на вывод; 1-я версия */
- main()
- {
- int c;
- c = getchar();
- while (c != EOF) {
- putchar(c);
- c = getchar();
- }
- }
- \end{LongCodePar}
- \noindent%
- \index{оператор!неравенства \texttt{"!=}}%
- \index{операторы!отношения}%
- Оператор отношения \verb|!=| означает <<не равно>>.
- Каждая литера, вводимая с клавиатуры или появляющаяся на экране, как и любая
- другая литера внутри машины, кодируется комбинацией бит. Тип \verb|char|
- специально предназначен для хранения литерных данных, однако для этого также
- годится и любой целый тип. Мы пользуемся типом \verb|int| и делаем это по одной
- важной причине, которая требует разъяснений.
- Существует проблема: как отличить конец ввода от обычных читаемых данных.
- Решение в том, чтобы функция \verb|getchar| по исчерпании входного потока
- выдавала в качестве результата такое значение, которое нельзя было бы спутать ни
- с одной реальной литерой. Это значение есть \verb|EOF| (аббревиатура от end of
- file -- конец файла). Мы должны объявить переменную \verb|c| такого типа, чтобы
- его <<хватило>> для представления всех возможных результатов, выдаваемых
- функцией \verb|getchar|. Нам не подходит тип \verb|char|, так как \verb|c|
- должна быть достаточно <<ёмкой>>, чтобы помимо любого значения типа \verb|char|
- быть в состоянии хранить и \verb|EOF|. Вот почему мы используем \verb|int|, а не
- \verb|char|.
- \index{файл!головной!<stdio.h>@\texttt{<stdio.h>}}%
- \index{EOF@\texttt{EOF}}%
- \index{stdio.h@\texttt{<stdio.h>}}%
- \verb|EOF| -- целая константа, определённая в \verb|<stdio.h>|. Какое значение
- имеет эта константа -- неважно, лишь бы оно отличалось от любого из возможных
- значений типа \verb|char|. Использование именованной константы с унифицированным
- именем гарантирует, что программа не будет зависеть от конкретного числового
- значения. которое, возможно, в других Си-системах будет иным.
- \index{инструкции!присваивания вложенные}%
- Программу копирования можно написать более сжато. В Си любое
- \index{выражение!присваивание}%
- \index{присваивание!выражение}%
- присваивание, например,
- \begin{ShortCodePar}
- c = getchar();
- \end{ShortCodePar}
- \noindent трактуется как выражение со значением, равным значению левой части
- после присваивания.
- \index{присваивание!инструкция вложенная}%
- Это значит, что присваивание может встречаться внутри более
- сложного выражения. Если присваивание переменной \verb|c| расположить в проверке
- условия цикла \verb|while|, то программу копирования можно будет записать в
- следующем виде:
- \begin{LongCodePar}
- #include <stdio.h>
- /* копирование ввода на вывод; 2-я версия */
- main()
- {
- int c;
- while ((c = getchar()) != EOF)
- putchar(c);
- }
- \end{LongCodePar}
- Цикл \verb|while|, пересылая в \verb|c| полученное от \verb|getchar| значение,
- сразу же проверяет: не является ли оно <<концом файла>>. Если это не так, тело
- цикла \verb|while| выполняется и литера печатается. По окончании ввода
- завершается работа цикла \verb|while| и тем самым и \verb|main|.
- В данной версии ввод <<централизован>> -- в программе имеется только одно
- обращение к \verb|getchar|. В результате она более компактна и легче
- воспринимается при чтении. Вам часто придётся сталкиваться с такой формой
- записи, где присваивание делается вместе с проверкой. (Чрезмерное увлечение ею,
- однако, может запутать программу, поэтому мы постараемся пользоваться указанной
- формой разумно.)
- \index{оператор!приоритет}%
- \index{приоритеты операторов}%
- Скобки внутри условия, обрамляющие присваивание, необходимы. \emph{Приоритет}
- \verb|!=| выше, чем приоритет \verb|=|, из чего следует, что при отсутствии
- скобок проверка \verb|!=| будет выполняться до операции присваивания \verb|=|.
- Таким образом, запись
- \begin{ShortCodePar}
- c = getchar() != EOF
- \end{ShortCodePar}
- \noindent эквивалентна записи
- \begin{ShortCodePar}
- c = (getchar() != EOF)
- \end{ShortCodePar}
- \noindent А это совсем не то, что нам нужно: переменной \verb|c| будет
- присваиваться $0$ или $1$ в зависимости от того, встретит или не встретит
- \verb|getchar| признак конца файла. (Более подробно об этом см. в
- гл.~\ref{chapt:types-operators-expressions}.)
- \index{программа!копирования файлов|)}%
- \paragraph{Упражнение 1.6.} Убедитесь в том, что выражение
- \verb|getchat() != EOF| получает значение $0$ или $1$.
- \paragraph{Упражнение 1.7.} Напишите программу, печатающую значение
- \verb|EOF|.
- \subsection{Подсчёт литер}
- \index{программа!подсчёта!литер}%
- Следующая программа занимается подсчётом литер; она имеет много сходных черт с
- программой копирования.
- \begin{LongCodePar}
- #include <stdio.h>
- /* подсчёт вводимых литер; 1-я версия */
- main()
- {
- long nc;
- nc = 0;
- while (getchar() != EOF)
- ++nc;
- printf("%1d\n", nc);
- }
- \end{LongCodePar}
- \index{оператор!инкрементации \texttt{++}}%
- Инструкция
- \begin{ShortCodePar}
- ++nc
- \end{ShortCodePar}
- \noindent демонстрирует новый оператор \verb|++|, который означает
- \emph{увеличить на единицу}. Вместо этого можно было бы написать
- \verb|nc = nc+1|, но \verb|++nc| намного короче, а часто и эффективнее.
- \index{оператор!декрементации \texttt{\textminus\textminus}}%
- Существует аналогичный оператор \verb|--|‚ означающий \emph{уменьшить на
- единицу}. Операторы \verb|++| и \verb|--| могут быть и префиксными (\verb|++nc|)
- и постфиксными (\verb|nc++|). Как будет показано в
- гл.~\ref{chapt:types-operators-expressions}, эти две формы имеют разные значения
- в выражениях, но и \verb|++nc|, и \verb|nc++| добавляют к \verb|nc| единицу. В
- данном случае мы остановились на префиксной записи.
- \index{граничные условия}%
- Программа подсчёта литер накапливает сумму в переменной типа \verb|long|.
- \index{числа!размер}%
- \index{long@\texttt{long}!тип}%
- Целые типа \verb|long| имеют не менее 32 бит. Хотя на некоторых машинах типы
- \verb|int| и \verb|long| имеют одинаковый размер, существуют, однако машины, в
- которых \verb|int| занимает 16 бит с максимально возможным значением $32767$, а
- это -- сравнительно маленькое число, и счётчик типа \verb|int| может
- переполниться.
- \index{ld преобразование@\texttt{{\%}ld} преобразование}%
- Спецификация преобразования \verb|%ld| в \verb|printf| указывает,
- что соответствующий аргумент имеет тип \verb|long|.
- \index{double@\texttt{double}!тип}%
- \index{float@\texttt{float}!тип}%
- Возможно охватить ещё больший диапазон значений, если использовать тип
- \verb|double| (т.е. \verb|float| с двойной точностью). Применим также
- инструкцию \verb|for| вместо \verb|while|, чтобы продемонстрировать другой
- способ написания цикла.
- \begin{LongCodePar}
- #include <stdio.h>
- /* подсчёт вводимых литер; 2-я версия */
- main()
- {
- double nc;
- for (nc = 0; getchar() != EOF; ++nc)
- ;
- printf("%.0f\n", nc);
- }
- \end{LongCodePar}
- \noindent В \verb|printf| спецификатор \verb|%f| применяется как для
- \verb|float|, так и для \verb|double|; спецификатор \verb|%.0f| означает печать
- без десятичной точки и дробной части (последняя в нашем случае отсутствует).
- \index{for@\texttt{for}!инструкция}%
- Тело указанного \verb|for|-цикла пусто, поскольку кроме проверок и приращений
- счётчика делать ничего не нужно. Но правила грамматики Си требуют, чтобы
- \verb|for|-цикл имел тело.
- \index{инструкция!пустая}%
- \index{точка с запятой}%
- Выполнение этого требования обеспечивает изолированная точка с запятой,
- называемая \emph{пустой инструкцией}. Мы поставили точку с запятой на отдельной
- строке для большей наглядности.
- Наконец, заметим, что, если ввод не содержит ни одной литеры, то при первом же
- обращении к \verb|getchar| условие в \verb|while| или \verb|for| не будет
- выполнено, и программа выдаст нуль, что и будет правильным результатом. Это
- важно. Одно из привлекательных свойств циклов \verb|while| и \verb|for| состоит
- в том, что условие проверяется до того, как выполняется тело цикла. Если ничего
- делать не надо, то ничего делаться и не будет, пусть даже тело цикла не будет
- выполнено ни разу. Программа должна вести себя корректно при нулевом количестве
- вводимых литер. Само устройство циклов \verb|while| и \verb|for| даёт
- дополнительную уверенность в правильном поведении программы в случае граничных
- условий.
- \subsection{Подсчёт строк}
- \index{программа!подсчёта!строк}%
- Следующая программа подсчитывает строки. Как упоминалось выше, стандартная
- библиотека обеспечивает такую модель ввода-вывода, при которой входной текстовый
- поток состоит из последовательности строк, каждая из которых заканчивается
- литерой новая-строка. Следовательно, подсчёт строк сводится к подсчёту числа
- литер новая-строка.
- \begin{LongCodePar}
- #include <stdio.h>
- /* подсчёт строк входного потока */
- main()
- {
- int c, nl;
- nl=0;
- while ((0 = getchar()) != EOF)
- if (c == '\n')
- ++nl;
- printf("%d\n", nl);
- }
- \end{LongCodePar}
- Тело цикла теперь образует инструкция \verb|if|, под контролем которой находится
- увеличение счётчика \verb|nl| на единицу.
- \index{if-else@\texttt{if-else}!инструкция}%
- Инструкция \verb|if| проверяет условие в скобках и, если оно истинно, выполняет
- следующую за ним инструкцию (или группу инструкций, заключённую в фигурные
- скобки).
- \index{отступы в тексте программы}%
- \index{программа!формат}%
- Мы опять делаем отступы в тексте программы, чтобы показать, что чем управляется.
- \index{оператор!равенства \texttt{==}}%
- Двойной знак равенства в языке Си обозначает оператор <<равно>> (он аналогичен
- оператору \verb|=| в Паскале и \verb|.EQ| в Фортране). Удваивание \verb|=| в
- операторе проверки на равенство сделано для того, чтобы отличить его от
- единичного \verb|=|, используемого в Си для обозначения присваивания.
- Предупреждаем: начинающие программировать на Си иногда пишут \verb|=|, а имеют в
- виду \verb|==|. Как мы увидим в гл.~\ref{chapt:types-operators-expressions}, в
- этом случае результатом обычно будет вполне допустимое по форме выражение, на
- которое компилятор не выдаст никаких предупреждающих сообщений.
- \index{литера!кавычка \texttt{'}}%
- Литера‚ заключённая в одиночные кавычки, представляет целое значение, равное
- коду этой литеры (в кодировке, принятой на данной машине).
- \index{константа!литерная}%
- Это так называемая \emph{литерная константа}. Существует и другой способ для
- написания маленьких целых значений. Например, \verb|'A'| есть литерная
- константа;
- \index{литеры!набор!ASCII}%
- \index{ASCII}%
- в наборе литер ASCII её значение равняется $65$ -- внутреннему представлению
- буквы A. Конечно, \verb|'A'| в роли константы предпочтительнее, чем $65$,
- поскольку смысл первой записи более очевиден, и она не зависит от конкретного
- способа кодировки литер.
- \index{эскейп-последовательность}%
- Эскейп-последовательности, используемые в стринговых константах, допускаются
- также и в литерных константах.
- \index{литера!новая-строка \texttt{\textbackslash n}}%
- \index{n@\texttt{\textbackslash n}}%
- Так, \verb|'\n'| обозначает код литеры новая-строка, который в ASCII равен $10$.
- \index{константа!стринговая}%
- Следует обратить особое внимание на то, что \verb|'\n'| обозначает одну литеру
- (код которой в выражении рассматривается как целое значение в то время как
- \verb|"\n"| -- стринговая константа, в которой чисто случайно указана одна
- литера. Более подробно различие между литерными и стринговыми константами
- разбирается в гл.~\ref{chapt:types-operators-expressions}.
- \paragraph{Упражнение 1.8.} Напишите программу для подсчёта пробелов, табуляций
- и новых-строк.
- \paragraph{Упражнение 1.9.} Напишите программу, копирующую литеры ввода в
- выходной поток и заменяющую подряд стоящие пробелы на один пробел.
- \paragraph{Упражнение 1.10.} Напишите программу, копирующую вводимые литеры в
- выходной поток с заменой литеры табуляции на \verb|\t|, литеры забоя на
- \verb|\b| и каждой обратной наклонной черты на \verb|\\|. Это сделает видимыми
- все литеры табуляции и забоя.
- \subsection{Подсчёт слов}
- \index{программа!подсчёта!слов}%
- Четвёртая из нашей серии полезных программ подсчитывает строки, слова и литеры,
- причём под словом здесь имеется в виду любой стринг литер, не содержащий в себе
- пробелов, табуляций и новых-строк. Эта программа является упрощённой версией
- программы wc системы UNIX.
- \begin{LongCodePar}
- #include <stdio.h>
- #define IN 1 /* внутри слова */
- #define OUT 0 /* вне слова */
- /* подсчёт строк, слов и литер */
- main()
- {
- int c, nl, nw, nc, state;
- state = OUT;
- nl = nw = nc = 0;
- while ((c = getchar()) != EOF) {
- ++nc;
- if (c == '\n')
- ++nl;
- if (c == ' ' || c == '\n' || c == '\t')
- state = OUT;
- else if (state == OUT) {
- state = IN;
- ++nw;
- }
- }
- printf("%d %d %d\n", nl, nw, nc);
- }
- \end{LongCodePar}
- \noindent Каждый раз, встречая первую литеру слова, программа изменяет значение
- счётчика слов на $1$. Переменная \verb|state| фиксирует текущее состояние --
- находимся мы внутри или вне слова. Вначале ей присваивается значение \verb|OUT|,
- что соответствует состоянию <<вне слова>>. Мы предпочитаем пользоваться
- именованными константами \verb|IN| и \verb|OUT|, а не собственно значениями $1$
- и $0$, чтобы сделать программу более понятной. В нашей маленькой программе этот
- приём мало что даёт, но в большой программе увеличение её ясности окупает
- незначительные дополнительные усилия, потраченные на то, чтобы писать программу
- в таком стиле с самого начала. Вы обнаружите, что большие изменения гораздо
- легче вносить в те программы, в которых магические числа встречаются только в
- виде именованных констант.
- \index{множественное присваивание}%
- \index{инструкции!присваивания вложенные}%
- \index{присваивание!множественное}%
- \index{присваивание!выражение}%
- Строка
- \begin{ShortCodePar}
- nl = nw = nc = 0;
- \end{ShortCodePar}
- \noindent устанавливает все три переменные в нуль. Такая запись не является
- какой-то особой конструкцией и допустима потому, что
- \index{выражение!присваивание}%
- \index{присваивание!инструкция вложенная}%
- присваивание есть выражение со своим собственным значением и
- \index{вычисление, порядок}%
- \index{порядок!вычислений}%
- операции присваивания выполняются справа налево. Указанная строка эквивалентна
- \begin{ShortCodePar}
- nl = (nw = (nc = 0));
- \end{ShortCodePar}
- \index{оператор!логического ИЛИ \texttt{"|"|}}%
- Оператор \verb+||+ означает ИЛИ, так что строка
- \begin{ShortCodePar}
- if (c == ' ' || c == '\n' || c == '\t')
- \end{ShortCodePar}
- \noindent читается как <<если \verb|c| есть пробел, \emph{или} \verb|c| есть
- новая-строка, \emph{или} \verb|c| есть табуляция>>. (Напомним, что
- эскейп-последовательность \verb|\t| обозначает литеру табуляции.)
- \index{вычисление, порядок}%
- \index{порядок!вычислений}%
- \index{оператор!логического И \texttt{\&\&}}%
- Существует также оператор \verb|&&|, означающий И. Его приоритет выше, чем
- приоритет \verb+||+. Выражения, связанные операторами \verb|&&| или \verb+||+,
- вычисляются слева направо; при этом гарантируется, что вычисления сразу
- прервутся, как только будет установлена истинность или ложность условия. Если
- \verb|c| есть пробел, то дальше проверять, является значение \verb|c| литерой
- новая-строка или же табуляции, не нужно. В этом частном случае данный способ
- вычислений не столь важен, но он имеет значение в более сложных ситуациях,
- которые мы вскоре рассмотрим.
- \index{if-else@\texttt{if-else}!инструкция}%
- В примере также встречается слово \verb|else|, которое указывает на
- альтернативные действия, выполняемые в случае, когда условие, указанное в
- \verb|if|, не является истинным. В общем виде условная инструкция записывается
- так:
- \begin{ShortCodeParWithCC}{\\\{\}}
- if (\textit{выражение})
- \textit{инструкция}\textsubscript{1}
- else
- \textit{инструкция}\textsubscript{2}
- \end{ShortCodeParWithCC}
- \noindent В конструкции \verb|if-else| выполняется одна и только одна из двух
- инструкций. Если \textit{выражение} истинно, то выполняется
- \textit{инструкция}$_{1}$, если нет, то -- \textit{инструкция}$_{2}$. Каждая из
- этих двух \textit{инструкций} представляет собой либо одну инструкцию, либо
- несколько, заключённых в фигурные скобки. В нашей программе после \verb|else|
- стоит инструкция \verb|if|, управляющая двумя такими инструкциями.
- \paragraph{Упражнение 1.11.} Как протестировать программу подсчёта слов? Какой
- ввод вероятнее всего не обнаружит ошибок, если они были допущены?
- \paragraph{Упражнение 1.12.} Напишите программу, которая печатает содержимое
- своего ввода, помещая по одному слову на каждой строке.
- \section{Массивы}
- \index{программа!подсчёта!литер}%
- \index{программа!подсчёта!пробельных литер}%
- А теперь напишем программу, подсчитывающую по отдельности каждую цифру,
- пробельные литеры (пробелы, табуляции и новые-строки) и все другие литеры. Это
- несколько искусственная программа, но она позволит нам в одном примере
- продемонстрировать ещё несколько возможностей языка Си. Имеется двенадцать
- категорий вводимых литер. Удобно все десять счётчиков цифр хранить в массиве, а
- не в виде десяти отдельных переменных. Вот один из вариантов этой программы:
- \begin{LongCodePar}
- #include <stdio.h>
- /* подсчёт цифр, пробельных и прочих литер */
- main()
- {
- int c, i, nwhite, nother;
- int ndigit[1O];
- nwhite = nother = 0;
- for (i = 0; i < 10; ++i)
- ndigit[i] = 0;
- while ((0 = getchar()) != EOF)
- if (c >= '0' && c <= '9')
- ++ndigit[c-'0'];
- else if (c == ' ' || c == '\n' || c == '\t')
- ++nwhite;
- else
- ++nother;
- printf("цифры =");
- for (i = 0; i < 10; ++i)
- printf(" %d", ndigit[i]);
- printf(", пробелы = %d, прочие = %d\n", nwhite, nother);
- }
- \end{LongCodePar}
- \noindent Если пропустить текст этой программы в качестве ввода, то будет
- напечатан следующий результат
- \begin{ShortCodePar}
- цифры = 9 3 0 0 0 0 0 0 1, пробелы = 141, прочие = 347
- \end{ShortCodePar}
- \index{массив!декларация}%
- \index{декларация!массива}%
- Декларация
- \begin{ShortCodePar}
- int ndigit[10];
- \end{ShortCodePar}
- \noindent определяет \verb|ndigit| как массив из 10 значений типа \verb|int|. В
- Си элементы массива всегда нумеруются, начиная с нуля, так что элементами этого
- массива будут \verb|ndigit[0]|, \verb|ndigit[1]|‚ \ldots, \verb|ndigit[9]|, что
- учитывается в \verb|for|-циклах (при инициализации и печати массива).
- \index{массив!индексирование}%
- \index{индексирование массива}%
- Индексом может быть любое целое выражение, образуемое целыми переменными
- (например, \verb|i|) и целыми константами.
- \index{преобразование!литера -- целое}%
- Приведённая программа опирается на определённые свойства кодировки цифр.
- Например, проверка
- \begin{ShortCodePar}
- if (c >= '0' && c <= '9') ...
- \end{ShortCodePar}
- \noindent определяет, является ли находящаяся в \verb|c| литера цифрой. Если это
- так, то
- \begin{ShortCodePar}
- c - '0'
- \end{ShortCodePar}
- \noindent есть числовое значение цифры. Сказанное справедливо только в том
- случае, если для ряда значений \verb|'0'|‚ \verb|'1'|, \ldots, \verb|'9'| каждое
- следующее значение на $1$ больше предыдущего. К счастью, это правило соблюдается
- во всех наборах литер.
- \index{преобразование!литера -- целое}%
- По определению значения типа \verb|char| являются всего лишь малыми целыми, так
- что переменные и константы типа: \verb|char| в арифметических выражениях
- идентичны значениям типа \verb|int|. Это и естественно, и удобно: например,
- \verb|c-'0'| есть целое выражение с возможными значениями от $0$ до $9$, которые
- соответствуют литерам от \verb|'0'| до \verb|'9'|, хранящимся в переменной
- \verb|c|. Таким образом, значение данного выражения является правильным индексом
- для массива \verb|ndigit|.
- Следующий фрагмент определяет, является литера цифрой, пробельной литерой или
- чем-нибудь иным.
- \begin{ShortCodePar}
- if (c >= '0' && c <= '9')
- ++ndigit[c-'0'];
- else if (c == ' ' || c == '\n' || c == '\t')
- ++nwhite;
- else
- ++nother
- \end{ShortCodePar}
- %%
- %% в предыдущем блоке кода в оригинале не было точки с запятой в конце
- %%
- \noindent%
- \index{многопутевое ветвление}%
- \index{else-if@\texttt{else-if}}%
- Конструкция вида
- \begin{LongCodeParWithCC}{\\\{\}}
- if (\textit{условие}\textsubscript{1})
- \textit{инструкция}\textsubscript{1}
- else if (\textit{условие}\textsubscript{2})
- \textit{инструкция}\textsubscript{2}
- ...
- ...
- else
- \textit{инструкция}\textsubscript{n}
- \end{LongCodeParWithCC}
- \noindent часто применяется для выбора одного из нескольких альтернативных
- путей, имеющихся в программе. \textit{Условия} вычисляются по порядку в
- направлении сверху вниз до тех пор, пока одно из них не будет удовлетворено; в
- этом случае соответствующая ему \textit{инструкция} будет выполнена, и работа
- всей конструкции завершится. (Любая из \textit{инструкций} может быть группой
- инструкций, обрамленных фигурными скобками.) Если ни одно из \textit{условий} не
- удовлетворено, выполняется последняя \textit{инструкция}, расположенная сразу за
- \verb|else|, если таковая имеется. Если же \verb|else| и следующей за ней
- \textit{инструкции} нет (как это было в программе подсчёта слов), то никакие
- действия вообще не производятся.
- Между первым \verb|if| и завершающим \verb|else| может быть сколько угодно
- комбинаций вида
- \begin{ShortCodeParWithCC}{\\\{\}}
- else if (\textit{условие})
- \textit{инструкция}
- \end{ShortCodeParWithCC}
- \index{отступы в тексте программы}%
- \index{программа!формат}%
- Когда их несколько, программу разумно форматировать так, как мы здесь показали.
- Если же каждый следующий \verb|if| сдвигать вправо относительно предыдущего
- \verb|else|, то при длинном каскаде проверок текст окажется слишком близко
- прижатым к правому краю страницы.
- Инструкция \verb|switch| речь о которой пойдёт в гл.~\ref{chapt:control_flow},
- обеспечивает другой способ изображения многопутевого ветвления на языке Си. Он
- более подходит, в частности, тогда, когда условием перехода служит совпадение
- значения некоторого выражения целочисленного типа с одной из констант, входящих
- в заданный набор. Вариант нашей программы, реализованной с помощью
- \verb|switch| приводится в разд.~\ref{sec:switch}.
- \paragraph{Упражнение 1.13.} Напишите программу, печатающую гистограммы длин
- вводимых слов. Гистограмму легко рисовать горизонтальными полосами. Рисование
- вертикальными полосами -- более трудная задача.
- \paragraph{Упражнение 1.14.} Напишите программу, печатающую гистограммы частот
- встречаемости вводимых литер.
- \section{Функции}
- \index{модульность}%
- \index{функция}%
- Функции в Си играют ту же роль, что и подпрограммы и функции в Фортране или
- процедуры и функции в Паскале. Функция обеспечивает удобный способ отдельно
- оформить некоторое вычисление и пользоваться им далее, не заботясь о том, как
- оно реализовано. После того как функции написаны можно забыть, \emph{как} они
- сделаны, достаточно знать лишь, \emph{что} они умеют делать. Механизм
- использования функций в Си удобен, лёгок и эффективен. Зачастую вы будете
- встречать короткие функции, вызываемые только единожды; они оформлены в виде
- функции с одной-единственной целью -- получить более ясную программу.
- До сих пор мы пользовались готовыми функциями типа \verb|printf|,
- \verb|getchar| и \verb|putchar|, теперь настала пора написать несколько наших
- собственных функций. В Си нет оператора возведения в степень типа оператора
- \verb|**| в Фортране.
- \index{функция!power@\texttt{power}}%
- Поэтому проиллюстрируем механизм определения функции на
- примере функции \verb|power(m,n)|, которая возводит целое \verb|m| в целую
- положительную степень \verb|n|. Так, \verb|power(2,5)| имеет значение $32$. На
- самом деле для практического применения эта функция малопригодна, так как она
- оперирует лишь малыми целыми степенями, однако она достаточно хороша, чтобы
- послужить иллюстрацией. (В стандартной библиотеке есть функция \verb|pow(x,y)|,
- вычисляющая $x^y$.)
- Итак, мы имеем функцию \verb|power| и главную функцию, пользующуюся её услугами,
- так что вся программа выглядит следующим образом:
- \begin{LongCodePar}
- #include <stdio.h>
- int power(int m, int n);
- /* тест функции power */
- main()
- {
- int i;
-
- for (i = 0; i < 10; ++i)
- printf("%d %d %d\n", i, power(2,i), power(-3,i));
- return 0;
- }
- /* возводит base в n-ю степень; n >= 0 */
- int power(int base, int n)
- {
- int i, p;
-
- p = 1;
- for (i = 1; i <= n; ++i)
- p = p * base;
- return p;
- }
- \end{LongCodePar}
- \index{определение!аргумента}%
- \index{определение!параметра}%
- \index{определение!функции}%
- \index{параметр!определение}%
- \index{функция!определение}%
- Определение любой функции имеет следующий вид:
- \begin{ShortCodeParWithCC}{\\\[\]}
- \textit[тип-результата]\
- \textit[имя-функции](\textit[список параметров,\
- если он есть])
- {
- \textit[декларации]
- \textit[инструкции]
- }
- \end{ShortCodeParWithCC}
- \noindent%
- \index{компиляция!Си-программ}%
- Определения функций могут располагаться в любом порядке в одном или в нескольких
- исходных файлах, но любая функция должна быть целиком расположена в каком-то
- одном. Если исходный текст программы распределён по нескольким файлам, то, чтобы
- её скомпилировать и загрузить, вам придётся сказать несколько больше, чем в
- случае одного файла; но это уже относится к операционной системе, а не к языку.
- Пока мы предполагаем, что обе функции находятся в одном файле, так что тех
- знаний, которые вы уже получили относительно запуска программ на Си, будет
- достаточно.
- В следующей строке из функции \verb|main| к \verb|power| обращаются дважды.
- \begin{ShortCodePar}
- printf("%d %d %d\n", i, power(2,i), power(-3,i));
- \end{ShortCodePar}
- \noindent При каждом вызове функции \verb|power| передаются два аргумента, и
- каждый раз главная программа в ответ получает целое число, которое затем
- приводится к должному формату и печатается. Как часть выражения
- \verb|power(2,i)| представляет собой целое значение точно так же, как \verb|2|
- или \verb|i|. (Не все функции в качестве результата выдают целые значения;
- подробно об этом будет сказано позже, в
- гл.~\ref{chapt:functions_and_program_structure}.)
- В первой строке определения \verb|power|:
- \begin{ShortCodePar}
- int power(int base, int n)
- \end{ShortCodePar}
- \noindent указываются типы параметров, имя функции и тип результата. Имена
- параметров локализованы в \verb|power|, и это значит, что они скрыты для любой
- другой функции, так что остальные подпрограммы могут свободно пользоваться ими
- для своих целей. Последнее утверждение справедливо также для переменных \verb|i|
- и \verb|p|: \verb|i| в \verb|power| и \verb|i| в \verb|main| не имеют между
- собой ничего общего.
- \index{функция!аргумент}%
- Далее \emph{параметром} мы будем называть переменную из списка параметров,
- заключённого в скобки и заданного в определении функции, а \emph{аргументом} --
- значение, используемое при обращении к функции. Иногда в том же смысле мы будем
- употреблять термины \emph{формальный аргумент} и \emph{фактический аргумент}.
- \index{return-инструкция@\texttt{return}-инструкция}%
- Значение, вычисляемое функцией \verb|power|, возвращается в \verb|main| с
- помощью инструкции \verb|return|. За словом \verb|return| может следовать любое
- выражение:
- \begin{ShortCodeParWithCC}{\\\{\}}
- return \textit{выражение};
- \end{ShortCodeParWithCC}
- \noindent Функция не обязательно возвращает какое-нибудь значение. Инструкция
- \verb|return| без выражения только передаёт управление в ту программу, которая
- её вызвала, не передавая ей никакого результирующего значения. То же самое
- происходит, если в процессе вычислений мы выходим на конец функции, отмеченный в
- тексте последней закрывающей фигурной скобкой. Возможна ситуация, когда
- вызывающая функция игнорирует возвращаемый ей результат.
- \index{main@\texttt{main}!возврат из}%
- \index{return@\texttt{return}!из \texttt{main}}%
- Вы, вероятно, обратили внимание на инструкцию \verb|return| в конце \verb|main|.
- Поскольку \verb|main| есть функция, как и любая другая она может вернуть
- результирующее значение тому, кто её вызвал, -- фактически в
- %%
- %% в оригинале есть типографская строка
- %% 2. Заказ № 13
- %%
- операционную среду, в которой была запущена программа. Обычно возвращается
- нулевое значение, что говорит о нормальном завершении счёта. Ненулевое значение
- сигнализирует о необычном или ошибочном завершении. До сих пор ради простоты мы
- опускали \verb|return| в \verb|main|, но с этого момента будем задавать
- \verb|return| как напоминание о том, что программы должны сообщать о состоянии
- своего завершения в операционную систему.
- \index{прототип функции}%
- \index{функция!прототип}%
- Декларация
- \begin{ShortCodePar}
- int power(int m, int n);
- \end{ShortCodePar}
- \noindent стоящая непосредственно перед \verb|main|, сообщает, что функция
- \verb|power| ожидает двух целых аргументов и возвращает целый результат. Это
- описание, называемое \emph{прототипом функции}, должно быть согласовано с
- определением и всеми вызовами \verb|power|. Будет ошибкой, если определение
- функции или вызов не соответствует своему прототипу.
- Имена параметров не требуют согласования. Фактически в прототипе они могут быть
- произвольными или вообще отсутствовать, т.е. прототип можно было бы записать и
- так:
- \begin{ShortCodePar}
- int power(int, int);
- \end{ShortCodePar}
- \noindent Однако хорошо подобранные имена поясняют программу, и мы будем часто
- этим пользоваться.
- \index{декларация!функции!старым способом}%
- \index{функция!в старом стиле}%
- Историческое замечание. Самые большие отличия ANSI-Си от более ранних версий
- языка как раз и заключаются в способах описания и определения функций. В первой
- версии Си функцию \verb|power| требовалось задавать в следующем виде:
- \begin{ShortCodePar}
- /* power возводит base в n-ю степень; n >= 0 */
- /* (версия в старом стиле языка Си) */
- power(base, n)
- int base, n;
- {
- int i, p;
- p = 1;
- for (i = 1; i <= n; ++i)
- p = p * base;
- return p;
- }
- \end{ShortCodePar}
- \noindent Здесь имена параметров перечислены в круглых скобках, а их типы заданы
- перед первой открывающей фигурной скобкой. В случае отсутствия указания о типе
- параметра, считается, что он имеет тип \verb|int|. (Тело функции не претерпело
- изменений.)
- Описание \verb|power| в начале программы согласно первой версии Си должно было
- бы выглядеть следующим образом:
- \begin{ShortCodePar}
- int power()
- \end{ShortCodePar}
- \noindent Нельзя было задавать список параметров, и поэтому компилятор не имел
- возможности проверить правильность обращений к \verb|power|.
- \index{декларация!функции!неявная}%
- \index{функция!неявная декларация}%
- Так как при отсутствии описания \verb|power| предполагалось, что функция
- возвращает целое значение, то в данном случае описание целиком можно было бы
- опустить.
- Новый синтаксис для прототипов функций облегчает компилятору обнаружение ошибок
- в количестве аргументов и их типах. Старый синтаксис описания и определения
- функции всё ещё допускается стандартом ANSI, по крайней мере на переходный
- период, но, если ваш компилятор поддерживает новый синтаксис, мы настоятельно
- рекомендуем пользоваться только им.
- \paragraph{Упражнение 1.15.} Перепишите программу преобразования температур,
- выделив само преобразование в отдельную функцию.
- \section{Аргументы. Вызов по значению}
- Одно свойство функций в Си, вероятно, будет в новинку для программистов, которые
- уже пользовались другими языками и Фортраном в частности.
- \index{вызов!по значению}%
- В Си все аргументы функции передаются <<по значению>>. Это следует понимать так,
- что вызываемой функции посылаются значения её аргументов во временных
- переменных, а не сами аргументы. Такой способ передачи аргументов несколько
- отличается от <<вызова по ссылке>> в Фортране и спецификации \verb|var| при
- параметре в Паскале, которые позволяют подпрограмме иметь доступ к самим
- аргументам, а не к их локальным копиям.
- Главное отличие в том, что в Си вызываемая функция не может непосредственно
- изменить переменную вызывающей функции: она может изменить только её частную,
- временную копию.
- Однако вызов по значению следует отнести к достоинствам языка, а не к его
- недостаткам. Благодаря этому свойству обычно удаётся написать более компактную
- программу, содержащую меньшее число <<чужих>> переменных, поскольку параметры
- можно рассматривать как должным образом инициализированные локальные переменные
- вызванной подпрограммы.
- \index{функция!power@\texttt{power}}%
- В качестве примера приведём ещё одну версию функции
- \verb|power|, в которой как раз использовано это свойство.
- \begin{LongCodePar}
- /* power возводит base в n-ю степень; n >= 0; версия 2 */
- int power(int base, int n)
- {
- int p;
- for (p = 1; n > 0; --n)
- p = p * base;
- return p;
- }
- \end{LongCodePar}
- \noindent Параметр \verb|n| выступает здесь в роли временной переменной, в
- которой циклом \verb|for| в убывающем порядке ведётся счёт числа шагов до тех
- пор, пока её значение не станет нулём. При этом отпадает надобность в
- дополнительной переменной \verb|i| в качестве счётчика цикла. Что бы мы ни
- делали с \verb|n| внутри \verb|power|, это не окажет никакого влияния на сам
- аргумент, копия которого была передана функции \verb|power| в её вызове.
- \index{вызов!по ссылке}%
- При желании можно сделать так, чтобы функция смогла изменить переменную в
- вызывающей программе.
- \index{переменная!адрес}%
- Последняя для этого должна передать \emph{адрес}
- подлежащей изменению переменной (указатель на переменную), а в вызываемой
- функции следует соответствующий параметр описать как указатель и организовать
- косвенный доступ через него к этой переменной. Все, что касается указателей, мы
- рассмотрим в гл.~\ref{chapt:pointers_and_arrays}.
- \index{массив!имя в роли аргумента}%
- Механизм передачи в качестве аргумента массива -- несколько иной. Когда имя
- массива используется как аргумент, то функции передаётся значение, которое
- является адресом начала этого массива; никакие элементы массива не копируются. С
- помощью индексирования
- %%
- %% в оригинале есть типографская строка
- %% 2*
- %%
- относительно полученного значения функция имеет доступ к любому элементу
- массива. Разговор об этом пойдёт в следующем разделе.
- \section{Массивы литер}
- \label{sec:char_arrays}
- \index{массив!литер}%
- Самый распространённый вид массива в Си -- массив литер.
- \index{программа!печати!самой длинной строки}%
- Чтобы проиллюстрировать использование литерных массивов и работающих с ними
- функций, напишем программу, которая читает набор текстовых строк и печатает
- самую длинную из них.
- Её схема достаточно проста:
- \begin{ShortCodeParWithCC}{\\\{\}}
- while (\textit{есть ли ещё строка?})
- if (\textit{данная строка длиннее самой длинной из предыдущих})
- \textit{запомнить её}
- \textit{запомнить её длину}
- \textit{напечатать самую длинную строку}
- \end{ShortCodeParWithCC}
- \noindent Из схемы видно, что программа естественным образом распадается на
- части. Одна из них получает новую строку, другая проверяет её, третья
- запоминает, а остальные управляют процессом вычислений.
- \index{модульность}%
- Поскольку процесс чётко распадается на части, его хорошо бы так и переводить на
- Си.
- \index{функция!getline@\texttt{getline}}%
- Поэтому сначала напишем отдельную функцию \verb|getline| для получения
- очередной строки. Мы попытаемся сделать эту функцию полезной и для других
- применений. Как минимум \verb|getline| должна сигнализировать о возможном конце
- файла, а ещё лучше, если она будет выдавать длину строки или нуль (в случае
- исчерпания файла). Нуль годится для признака конца файла, поскольку не бывает
- строк нулевой длины, -- даже строка, содержащая только одну литеру новая-строка
- имеет длину 1.
- Когда мы обнаружим строку более длинную, чем самая длинная из всех предыдущих,
- то нам надо будет где-то её запомнить.
- \index{функция!copy@\texttt{copy}}%
- Здесь напрашивается вторая функция, \verb|copy|, которая умеет копировать новую
- строку в надёжное место.
- Наконец, нам необходима главная программа, которая бы управляла функциями
- \verb|getline| и \verb|copy|. Вот как выглядит наша программа в целом:
- \begin{LongCodePar}
- #include <stdio.h>
- #define MAXLINE 1000 /* макс-ный размер вводимой строки */
- int getline(char line[], int maxline);
- void copy(char to[], char from[]);
- /* печать самой длинной строки */
- main()
- {
- int len; /* длина текущей строки */
- int max; /* длина максимальной из просмотренных строк */
- char line[MAXLINE]; /* текущая строка */
- char longest[MAXLINE]; /* самая длинная строка */
- max = 0;
- while ((len = getline(line, MAXLINE)) > 0)
- if (len > max) {
- max = len;
- copy(longest, line);
- }
- if (max > 0) /* была ли хоть одна строка? */
- printf("%s", longest);
- return 0;
- }
- /* getline: читает строку в s, возвращает длину */
- int getline(char s[], int lim)
- {
- int c, i;
- for (i=0; i<lim-1 && (c=getchar())!=EOF && c!='\n'; ++i)
- s[i] = c;
- if (c == '\n') {
- s[i] = c;
- ++i;
- }
- s[i] = '\0';
- return i;
- }
- /* copy: копирует из 'from' в 'to'; to достаточно большой */
- void copy(char to[], char from[])
- {
- int i;
- i = 0;
- while ((to[i] = from[i]) != '\0')
- ++i;
- }
- \end{LongCodePar}
- Мы предполагаем, что функции \verb|getline| и \verb|copy|, описанные в начале
- программы, находятся в том же файле, что и \verb|main|.
- Функции \verb|main| и \verb|getline| взаимодействуют между собой через пару
- аргументов и возвращаемое значение.
- \index{прототип функции}%
- \index{функция!прототип}%
- В \verb|getline| аргументы определяются строкой
- \begin{ShortCodePar}
- int getline(char s[], int lim)
- \end{ShortCodePar}
- \noindent Как мы видим, её первый аргумент, \verb|s|, есть массив, а второй,
- \verb|lim|, имеет целый тип. Задание размера массива в определении имеет целью
- резервирование памяти. В самой \verb|getline| задавать длину массива \verb|s|
- нет необходимости, так как его размер указан в \verb|main|.
- \index{return-инструкция@\texttt{return}-инструкция}%
- Чтобы вернуть значение вызывающей программе, \verb|getline| использует
- \verb|return| точно так, как это делает функция \verb|power|.
- \index{по умолчанию!тип функции}%
- \index{функция!тип по умолчанию}%
- В приведённой строке также сообщается, что \verb|getline| возвращают значение
- типа \verb|int|, но так как при отсутствии указания о типе подразумевается
- \verb|int|, то перед \verb|getline| слово \verb|int| можно опустить.
- Одни функции возвращают результирующее значение, другие (как \verb|copy|) нужны
- только для того, чтобы произвести какие-то действия, не выдавая никакого
- значения.
- \index{void@\texttt{void}!тип}%
- На месте типа результата в \verb|copy| стоит \verb|void|. Это явное указание на
- то, что никакого значения данная функция не возвращает.
- Функция \verb|getline| в конец создаваемого ею массива помещает
- \index{null-литера, 0@null-литера, \texttt{\textbackslash 0}}%
- литеру \verb|'\0'| (\emph{null-литеру}, кодируемую нулевым байтом), чтобы
- пометить конец стринга литер. То же соглашение относительно окончания
- null-литерой соблюдается и в случае стринга литер типа
- \begin{ShortCodePar}
- "hello\n"
- \end{ShortCodePar}
- \noindent В данном случае для него формируется массив из литер этого стринга с
- \verb|'\0'| в конце.
- \begin{center}
- \begin{tabular}{|c|c|c|c|c|c|c|}
- \hline
- \verb| h |&\verb| e |&\verb| l |&\verb| l |&\verb| o |&
- \verb|\n |&\verb|\0 |\\
- \hline
- \end{tabular}
- \end{center}
- \noindent Спецификация \verb|%s| в формате \verb|printf| предполагает, что
- соответствующий ей аргумент -- стринг, оформленный указанным выше образом.
- Функция \verb|copy| в своей работе также опирается на тот факт, что читаемый ею
- аргумент заканчивается литерой \verb|'\0'|‚ который она копирует наряду с
- остальными литерами. (Все сказанное предполагает, что \verb|'\0'| не встречается
- внутри обычного текста.)
- Попутно стоит заметить, что даже на такой маленькой программе выявляются
- некоторые конструктивные трудности. Например, что должна делать \verb|main|,
- если встретится строка, превышающая допустимый размер? Функция \verb|getline|
- работает надёжно: если массив полон, она прекращает пересылку, даже если литеры
- новая-строка не обнаружила. Получив от \verb|getline| длину строки и увидев, что
- она совпадает с \verb|MAXLINE|, главная программа могла бы <<отловить>> этот
- особый случай и справиться с ним. В интересах краткости мы его здесь опускаем.
- Пользователи \verb|getline| не могут заранее узнать, сколь длинными будут
- вводимые строки, поэтому \verb|getline| делает проверки на переполнение. А вот
- пользователям функции \verb|copy| размеры копируемых стрингов известны (или
- они могут их узнать), поэтому дополнительный контроль здесь не нужен.
- \paragraph{Упражнение 1.16.} Перепишите \verb|main| предыдущей программы так,
- чтобы она могла печатать самую длинную строку без каких-либо ограничений на её
- размер.
- \paragraph{Упражнение 1.17.} Напишите программу печати всех вводимых строк,
- содержащих более 80 литер.
- \paragraph{Упражнение 1.18.} Напишите программу, которая будет в каждой вводимой
- строке заменять подряд стоящие литеры пробелов и табуляций на один пробел и
- удалять пустые строки.
- \paragraph{Упражнение 1.19.} Напишите функцию \verb|reverse(s)|, размещающую
- литеры в стринге \verb|s| в обратном порядке. Примените её при написании
- программы, которая <<реверсирует>> каждую вводимую строку.
- \section{Внешние переменные и область действия}
- \index{декларация!внешней переменной}%
- Переменные \verb|line|, \verb|longest| и т.д. принадлежат только функции
- \verb|main|, или, как говорят, локализованы в ней. Поскольку они определены
- внутри \verb|main|, никакие другие функции прямо к ним обращаться не могут. То
- же верно и применительно к переменным других функций; например, \verb|i| в
- \verb|getline| не имеет никакого отношения к \verb|i| в \verb|copy|.
- \index{инициализация!переменных!автоматических}%
- \index{память!автоматическая}%
- \index{переменная!автоматическая}%
- Каждая локальная переменная функции возникает только в момент обращения к этой
- функции и исчезает после выхода из неё. Вот почему такие переменные, следуя
- терминологии других языков, называют \emph{автоматическими}. (В
- гл.~\ref{chapt:functions_and_program_structure} обсуждается класс памяти
- \verb|static|, который позволяет локальным переменным сохранять свои значения в
- промежутках между вызовами.)
- Так как автоматические переменные образуются и исчезают одновременно с входом в
- функцию и выходом из неё, они не сохраняют своих значений от вызова к вызову и
- должны устанавливаться заново при каждом новом обращении к функции. Если этого
- не делать, они будут содержать <<мусор>>.
- \index{переменная!внешняя}%
- В качестве альтернативы автоматическим переменным можно определить
- \emph{внешние} переменные, к которым разрешается обращаться по их именам из
- любой функции. (Этот механизм аналогичен области \verb|COMMON| в Фортране и
- определениям переменных в самом внешнем блоке в Паскале.) Так как внешние
- переменные доступны повсеместно, их можно использовать вместо аргументов для
- связи между функциями по данным. Кроме того, поскольку внешние переменные
- существуют постоянно, а не возникают и исчезают на период активизации функции,
- свои значения они сохраняют и после возврата из функций, их установивших.
- \index{определение!внешней переменной}%
- \index{спецификатор!extern@\texttt{extern}}%
- Внешняя переменная должна быть \emph{определена}, причём только один раз, вне
- текста любой функции; в этом случае ей будет выделена память. Она должна быть
- \emph{описана} (\emph{декларирована}) во всех функциях, которые хотят ею
- пользоваться. Описание содержит сведения о типе переменной.
- \index{extern@\texttt{extern}}%
- Описание может быть явным, в виде инструкции \verb|extern|, или неявным, когда
- нужная информация получается из контекста.
- \index{программа!печати!самой длинной строки}%
- Чтобы конкретизировать сказанное перепишем программу печати самой длинной строки
- с использованием \verb|line|, \verb|longest| и \verb|max| в качестве внешних
- переменных.
- \index{функция!copy@\texttt{copy}}%
- \index{функция!getline@\texttt{getline}}%
- Это потребует изменений в вызовах, описаниях и телах всех трёх функций.
- \begin{LongCodePar}
- #include <stdio.h>
- #define MAXLINE 1000 /* макс-ный размер вводимой строки */
- int max; /* длина максимальной из просмотренных строк */
- char line[MAXLINE]; /* текущая строка */
- char longest[MAXLINE]; /* самая длинная строка */
- int getline(void);
- void copy(void);
- /* печать самой длинной строки */
- main()
- {
- int len;
- extern int max;
- extern char longest[];
- max = 0;
- while ((len = getline()) > 0)
- if (len > max) {
- max = len;
- copy();
- }
- if (max > 0) /* была хотя бы одна строка */
- printf("%s", longest);
- return 0;
- }
- /* getline: специализированная версия */
- int getline(void)
- {
- int c, i;
- extern char line[];
- for (i = 0; i < MAXLINE-1
- && (c=getchar()) != EOF && c != '\n'; ++i)
- line[i] = c;
- if (c == '\n') {
- line[i] = c;
- ++i;
- }
- line[i] = '\0';
- return i;
- }
- /* copy: специализированная версия */
- void copy(void)
- {
- int i;
- extern char line[], longest[];
- i = 0;
- while ((longest[i] = line[i]) != '\0')
- ++i;
- }
- \end{LongCodePar}
- Внешние переменные для \verb|main|, \verb|getline| и \verb|copy| определяются в
- начале нашего примера, где им присваивается тип и выделяется память. Определения
- внешних переменных синтаксически ничем не отличаются от определения локальных
- переменных, но поскольку они расположены вне функций, эти переменные считаются
- внешними. Чтобы функция могла пользоваться внешней переменной, ей нужно прежде
- всего сообщить имя соответствующей переменной.
- \index{спецификатор!extern@\texttt{extern}}%
- \index{extern@\texttt{extern}}%
- Это можно сделать, например, задав описание \verb|extern|, которое по виду
- отличается от определения внешней переменной только тем, что оно начинается с
- ключевого слова \verb|extern|.
- \index{спецификатор!extern@\texttt{extern}}%
- \index{extern@\texttt{extern}}%
- В некоторых случаях описание \verb|extern| можно опустить. Если определение
- внешней переменной в исходном файле расположено выше функции, где она
- используется, то в описании \verb|extern| нет необходимости. Описания
- \verb|extern|, таким образом, в \verb|main|, \verb|getline| и \verb|copy|
- избыточны. Обычно определения внешних переменных располагают в начале исходного
- файла, и все описания \verb|extern| для них опускают.
- \index{файл!головной}%
- Если же программа расположена в нескольких исходных файлах и внешняя переменная
- определена в \textit{файле1}, а используется в \textit{файле2} и
- \textit{файлеЗ}, то \verb|extern|-описания в \textit{файле2} и \textit{файлеЗ}
- обязательны, поскольку необходимо указать, что во всех трёх файлах функции
- ссылаются на одну и ту же внешнюю переменную.
- \index{include@\texttt{{\#}include}}%
- На практике обычно удобно собрать
- все описания внешних переменных и функций в отдельный файл, называемый
- \emph{головным} (\emph{header-файлом}), и помещать его с помощью \verb|#include|
- в начало каждого исходного файла.
- \index{файл!суффикс имени \texttt{.h}}%
- \index{h (суффикс имени файла)@\texttt{.h} (суффикс имени файла)}%
- Суффикс \verb|.h| по общей договорённости используется для имён header-файлов. В
- головных файлах, в \verb|<stdio.h>| в частности, описываются также функции
- стандартной библиотеки. Более подробно о головных файлах говорится в
- гл.~\ref{chapt:functions_and_program_structure}, а применительно к стандартной
- библиотеке -- в гл.~\ref{chapt:input-output} и приложении~\ref{apx:stdlib}.
- Так как специализированные версии \verb|getline| и \verb|copy| не имеют
- аргументов, на первый взгляд кажется, что логично их прототипы задать в виде
- \verb|getline()| и \verb|copy()|.
- \index{декларация!функции!старым способом}%
- \index{функция!в старом стиле}%
- Но из соображений совместимости со старыми Си-программами стандарт рассматривает
- пустой список как сигнал к тому, чтобы выключить все проверки на соответствие
- аргументов.
- \index{void@\texttt{void}!список аргументов}%
- Поэтому, когда нужно сохранить контроль и явно указать отсутствие аргументов,
- следует пользоваться словом \verb|void|. Мы вернёмся к этой проблеме
- в гл.~\ref{chapt:functions_and_program_structure}.
- \index{модульность}%
- \index{декларация!а не определение}%
- Заметим, что по отношению к внешним переменным в этом разделе мы очень аккуратно
- используем понятия \emph{определение} и \emph{описание}. <<Определение>>
- располагается в месте, где переменная создаётся и ей отводится память;
- <<описание>> помещается там, где фиксируется природа переменной, но никакой
- памяти для неё не отводится.
- Следует отметить тенденцию все переменные делать внешними. Дело в том, что, как
- может показаться на первый взгляд, это приводит к упрощению связей -- ведь
- списки аргументов становятся короче, а переменные везде доступны, где они нужны;
- правда, они оказываются доступными и там, где они не нужны. Однако чрезмерный
- акцент на внешние переменные чреват большими опасностями -- он приводит к
- программам, в которых связи по данным не очевидны, поскольку переменные могут
- неожиданным и даже таинственным способом изменяться. Кроме того, такая программа
- с трудом поддаётся модификациям. Вторая версия программы поиска самой длинной
- строки хуже, чем первая, частично по этим причинам, а частично из-за нарушения
- общности двух полезных функций, вызванного тем, что в них вписаны имена
- конкретных переменных, с которыми они оперируют.
- Итак, мы рассмотрели то, что можно было бы назвать ядром Си. Описанных
- <<кирпичиков>> достаточно, чтобы создавать полезные программы значительных
- размеров, и было бы чудесно, если бы вы, прервав чтение, посвятили этому
- какое-то время. В следующих упражнениях мы предлагаем вам реализовать несколько
- более сложные программы, чем те, что рассматривались выше.
- \paragraph{Упражнение 1.20.}\phantomsection\label{parag:exc1.20} Напишите
- программу \verb|detab|, заменяющую литеры табуляции вводимого текста нужным
- числом пробелов (до следующего <<стопа>> табуляции). Предполагается, что
- <<стопы>> табуляции расставлены на фиксированном расстоянии друг от друга,
- скажем, через \verb|n| позиций. Как лучше задавать \verb|n| -- в виде значения
- переменной или в виде именованной константы?
- \paragraph{Упражнение 1.21.}\phantomsection\label{parag:exc1.21} Напишите
- программу \verb|entab|, заменяющую цепочки пробелов минимальным числом литер
- табуляций и пробелов таким образом, чтобы вид напечатанного текста не изменился.
- Используйте те же <<стопы>> табуляции, что и в \verb|detab|. В случае, когда для
- выхода на очередной <<стоп>> годится один пробел, что лучше -- пробел или литера
- табуляции?
- \paragraph{Упражнение 1.22.} Напишите программу, печатающую литеры входного
- потока так, чтобы строки текста не выходили правее $n$-й позиции. Это значит,
- что каждая строка, длина которой превышает $n$, должна печататься с переносом на
- следующие строки. Место переноса следует <<искать>> после последней непробельной
- литеры, расположенной левее \verb|n|-й позиции. Позаботьтесь о том, чтобы ваша
- программа вела себя разумно в случае очень длинных строк, а также, когда до
- \verb|n|-й позиции не встречается ни одной литеры пробела или табуляции.
- %%
- %% разные шрифты для n в предыдущем абзаце это особенность оригинала
- %%
- \paragraph{Упражнение 1.23.} Напишите программу, убирающую все комментарии из
- любой Си-программы. Не забудьте должным образом обработать стринги литер и
- стринговые константы. Комментарии в Си не могут быть вложены друг в друга.
- \paragraph{Упражнение 1.24.} Напишите программу, проверяющую Си-программы на
- элементарные синтаксические ошибки типа несбалансированности скобок всех видов.
- Не забудьте о кавычках (одиночных и двойных), эскейп-последовательностях
- (\verb|\|\ldots) и комментариях. (Это -- сложная программа, если её писать для
- общего случая.)
|