Главная страница

Б. Керриган, Д. Ритчи Язык программирования C. Б. Керниган, Д. зык программирования и . Издание 3е, исправленное Перевод с английского под редакцией Вс. С. Штаркмана СанктПетербург 2003


Скачать 31.48 Mb.
НазваниеБ. Керниган, Д. зык программирования и . Издание 3е, исправленное Перевод с английского под редакцией Вс. С. Штаркмана СанктПетербург 2003
АнкорБ. Керриган, Д. Ритчи Язык программирования C.pdf
Дата06.04.2017
Размер31.48 Mb.
Формат файлаpdf
Имя файлаБ. Керриган, Д. Ритчи Язык программирования C.pdf
ТипКнига
#4546
страница15 из 28
1   ...   11   12   13   14   15   16   17   18   ...   28
Упражнение 6.5. Напишите функцию удаляющую имя и опре- деление из таблицы, организация которой поддерживается функциями lookup и install.
Упражнение 6.6. Реализуйте простую версию tfdefine-процессора (без которая использовала бы программы этого параграфа и годилась бы для Си-программ. Вам могут помочь программы getch и ungetch.
6.7. Средство typedef
Язык Си предоставляет называемое typedef, которое позво- ляет давать типам данных новые имена. Например, объявление typedef int Length;
делает имя Length синонимом int. С этого момента тип Length можно при- менять в объявлениях, в операторе приведения и д. точно так же, как тип
Length
Length

188 _ Глава 6. Структуры
Аналогично объявление char делает String синонимом char т. е. указателем на char, и правомерным будет, например, следующее его использование:
String р, lineptr[MAXLINES], alloc(int);
int p = (String)
что объявляемый в typedef тип стоит на месте имени пере- менной в обычном объявлении, а не сразу за словом
С точки зре- ния синтаксиса слово typedef напоминает класс памяти - static и т. д. Имена типов записаны с заглавных букв для того, чтобы они вы- делялись.
Для демонстрации более сложных примеров применения typedef вос- пользуемся этим средством при задании узлов деревьев, с которыми мы уже встречались в данной главе.
typedef struct tnode typedef struct tnode { /* узел дерева: */
char
/* указатель на текст */
int count; /* число вхождений */
left; /* левый сын */
right; /* правый сын */
} Treenode;
В результате создаются два новых названия типов: Treenode (структура)
и
(указатель на структуру). Теперь программу talloc можно за- писать в следующем виде:
Treeptr talloc(void)
{
return (Treeptr)
Следует подчеркнуть, что объявление typedef не создает объявления нового типа, оно лишь сообщает новое имя уже существующему типу.
Никакого нового смысла эти новые имена не несут, они объявляют пере- менные в точности с теми же свойствами, как если бы те были объявлены напрямую без переименования типа. Фактически typedef аналогичен с тем лишь отличием, что при интерпретации компилятором он

6.8. Объединения
может справиться с такой текстовой подстановкой, которая не может быть обработана препроцессором. Например typedef int (*PFI)(char *, char *);
создает тип
- "указатель на функцию (двух аргументов типа
*),
возвращающую int", который, например, в программе сортировки, опи- санной в главе 5, можно использовать в таком контексте:
PFI
Помимо просто эстетических соображений, для применения typedef существуют две важные причины. Первая - параметризация программы,
связанная с проблемой переносимости. Если с помощью typedef объявить типы данных, которые, возможно, являются машинно-зависимыми, то при переносе программы на другую машину потребуется внести изменения только в определения
Одна из распространенных ситуаций - использование typedef-имен для варьирования целыми величинами.
Для каждой конкретной машины это предполагает соответствующие уста- новки int или long, которые делаются аналогично установкам стан- дартных типов, например size_t и
Вторая причина, побуждающая к применению желание сде- лать более ясным текст программы. Тип, названный
г (от английс- ких слов t гее - дерево и pointer - указатель), более чем тот же тип, записанный как указатель на некоторую сложную структуру.
6.8. Объединения
Объединение - это переменная, которая может содержать (в разные моменты времени) объекты различных типов и размеров. Все требования относительно размеров и выравнивания выполняет компилятор. Объе- динения позволяют хранить разнородные данные в одной и той же обла- сти памяти без включения в программу машинно-зависимой информа- ции. Эти средства аналогичны вариантным записям в Паскале.
Примером использования объединений мог бы послужить сам компи- лятор, заведующий таблицей символов, если предположить, что констан- та может иметь тип int,
или являться указателем на символ и иметь тип
Значение каждой конкретной константы должно храниться в переменной соответствующего этой константе типа. Работать с табли- цей символов всегда удобнее, если значения занимают одинаковую по объ- ему память и запоминаются в одном и том же месте независимо от своего типа. Цель введения в программу объединения - иметь переменную, ко- торая бы на законных основаниях хранила в себе значения нескольких
\

Глава
Структуры
типов. Синтаксис объединений аналогичен синтаксису структур. Приве- дем пример объединения.
union u_tag {
int float char *sval;
} u;
Переменная будет достаточно большой, чтобы в ней поместилась любая переменная из указанных трех типов; точный ее размер зависит от реализации. Значение одного из этих трех типов может быть присвоено переменной и далее использовано в если это правомерно,
т. е. если тип взятого ею значения совпадает с типом последнего присво- енного ей значения. Выполнение этого требования в каждый текущий момент - целиком на совести программиста. В том случае, если нечто за- помнено как значение одного типа, а извлекается как значение другого типа, результат зависит от реализации.
Синтаксис доступа к элементам объединения следующий:
имя-объединения . элемент
или
-> элемент
т. е. в точности такой, как в структурах. Если для хранения типа текущего значения использовать, скажем, переменную то можно написать такой фрагмент программы:
if (utype == INT)
else if (utype == FLOAT)
else if (utype == STRING)
u.sval);
else printf ("неверный тип %d в
Объединения могут входить в структуры и массивы, и наоборот. За- пись доступа к элементу объединения, находящегося в структуре (как и структуры, находящейся в объединении), такая же, как и для вложенных структур. Например, в массиве структур struct {
char int flags;

6.9. Битовые поля 191
utype;
union {
int float char *sval;
}
}
к ival обращаются следующим образом:
а к первому символу строки можно обратиться любым из следую- щих двух способов:
u. sval
Фактически объединение - это структура, все элементы которой име- ют нулевое смещение относительно ее базового адреса и размер которой позволяет поместиться в ней самому большому ее элементу, а выравни- вание этой структуры удовлетворяет всем типам объединения. Операции,
применимые к структурам, годятся и для объединений, т. е. законны при- сваивание объединения и копирование его как единого целого, взятие адреса от объединения и доступ к отдельным его элементам.
Инициализировать объединение можно только значением, имеющим тип его первого элемента; таким образом, упомянутую выше переменную u можно инициализировать лишь значением типа int.
В главе 8 (на примере программы, заведующей выделением памяти) мы покажем, как, применяя объединение, можно добиться, чтобы расположе- ние переменной было выровнено по соответствующей границе в памяти.
6.9. Битовые поля
При дефиците памяти может возникнуть необходимость запаковать несколько объектов в одно слово машины. Одна из обычных ситуаций,
встречающаяся в задачах обработки таблиц символов для компилято- ров, - это объединение групп однобитовых флажков. Форматы некото- рых данных могут от нас вообще не зависеть и диктоваться, например,
интерфейсами с аппаратурой внешних устройств; здесь также возникает потребность адресоваться к частям слова.
Вообразим себе фрагмент компилятора, который заведует таблицей сим- волов. Каждый идентификатор программы имеет некоторую связанную

Глава 6. Структуры
с ним информацию: например, представляет ли он собой ключевое слово и, если это к какому классу принадлежит: внешняя и или ста- тическая и т. д. Самый компактный способ кодирования такой информа- ции - расположить однобитовые флажки в одном слове типа char или int.
Один из распространенных приемов работы с битами основан на опре- делении набора "масок", соответствующих позициям этих битов, как, на- пример, в
KEYWORD 01 /* ключевое слово */
EXTERNAL 02 /* внешний */
STATIC 04 /* статический */
или в
{ KEYWORD = 01, EXTERNAL = 02, STATIC = 04 };
Числа должны быть степенями двойки. Тогда доступ к битам становится делом "побитовых операций", описанных в главе 2 (сдвиг, маскирование,
взятие дополнения).
Некоторые виды записи выражений встречаются довольно часто. Так,
flags
EXTERNAL ! STATIC:
устанавливает 1 в соответствующих битах переменной flags &=
i
обнуляет их, а
if
& (EXTERNAL i оценивает условие как истинное, если оба бита нулевые.
Хотя научиться писать такого рода выражения не составляет труда,
вместо побитовых логических операций можно пользоваться предостав- ляемым Си другим способом прямого определения и доступа к полям внутри слова. Битовое поле (или для краткости просто поле) - это неко- торое множество битов, лежащих рядом внутри одной, зависящей от ре- ализации единицы памяти, которую мы будем называть "словом". Син- таксис определения полей и доступа к ним базируется на синтаксисе структур. Например, строки tfdef ine, фигурировавшие выше при задании таблицы символов, можно заменить на определение трех полей:
struct {
unsigned int
: 1;
unsigned int is_extern : 1;
unsigned int is_static :
> flags;

6.9. Битовые поля
Эта запись определяет переменную которая содержит три одноби- товых поля. Число, следующее за двоеточием, задает ширину поля. Поля объявлены как unsigned int, чтобы они воспринимались как беззнаковые
На отдельные поля ссылаются же, как и на элементы обычных струк- тур: flags.
т. д. Поля "ведут себя" как малые целые и могут участвовать в арифметических выражениях точно так же,
как и другие целые. Таким образом, предыдущие примеры можно напи- сать более естественно:
=
=
устанавливает 1 в соответствующие биты;
=
=
их обнуляет, а if
== 0 &&
== 0)
проверяет их.
Почти все технические детали, касающиеся полей, в частности, воз- можность поля перейти границу слова, зависят от реализации. Поля мо- гут не иметь имени; с помощью безымянного поля (задаваемого только двоеточием и шириной) организуется пропуск нужного количества раз- рядов. Особая ширина, равная используется, когда требуется вый- ти на границу следующего слова.
На одних машинах поля размещаются слева направо, на других - справа налево. Это значит, что при всей полезности работы с ними, если формат данных, с которыми мы имеем дело, дан нам свыше, то необходимо са- мым тщательным образом исследовать порядок расположения полей;
программы, зависящие от такого рода вещей, не переносимы. Поля мож- но определять только с типом int, а для того чтобы обеспечить переноси- мость, надо явно указывать signed или unsigned. Они не могут быть мас- сивами и не имеют адресов, и, следовательно, оператор & к ним не приме- ним.

Глава 7
Ввод и вывод
Возможности для ввода и вывода не являются частью самого языка Си,
поэтому мы подробно и не рассматривали их до сих пор. Между тем ре- альные программы взаимодействуют со своим окружением гораздо более сложным способом, чем те, которые были затронуты ранее. В этой главе мы опишем стандартную содержащую набор функций, обес- печивающих ввод-вывод, работу со строками, управление памятью, стан- дартные математические функции и разного рода сервисные Си-програм- мы. Но особое внимание уделим вводу-выводу.
Библиотечные функции точно определяются стандартом так что они совместимы на любых системах, где поддерживается
Си. Программы, которые в своем взаимодействии с системным окруже- нием не выходят за рамки возможностей стандартной библиотеки, мож- но без изменений переносить с одной машины на другую.
Свойства библиотечных функций специфицированы в более чем дю- жине заголовочных файлов; вам уже встречались некоторые из них, том числе ,
и Мы не рассматриваем здесь всю библиотеку, так как нас больше интересует написание Си-программ, чем использование библиотечных функций. Стандартная библиотека по- дробно описана в приложении В.
Стандартный ввод-вывод
Как уже говорилось в главе библиотечные функции реализуют про- стую модель текстового ввода-вывода. Текстовый поток состоит из пос- ледовательности строк; каждая строка заканчивается символом новой строки. Если система в чем-то не следует принятой модели, библиотека сделает так, чтобы казалось, что эта модель удовлетворяется полностью.
Например, пара символов - возврат-каретки и перевод-строки - при вво-

Стандартный ввод-вывод де могла бы быть преобразована в один символ новой строки, а при выво- де выполнялось бы обратное преобразование.
Простейший механизм ввода - это чтение одного символа из стандарт-
ного ввода (обычно с клавиатуры) функцией get int
В качестве результата каждого своего вызова функция getcha r возвраща- ет следующий символ ввода или, если обнаружен конец файла,
Име- нованная константа EOF (аббревиатура от
of file - конец файла) опре- делена в h>. Обычно значение EOF равно но, чтобы не зависеть от конкретного значения этой константы, обращаться к ней следует по имени (EOF).
Во многих системах клавиатуру можно заменить файлом, перенапра- вив ввод с помощью значка <. Так, если программа год использует getcha г,
то командная строка prog < infile предпишет программе prog читать символы из а не с клавиатуры.
Переключение ввода делается так, что сама программа не замечает подмены; в частности строка не будет включена в командной строки
Переключение ввода будет также незаметным,
если ввод исходит от другой программы и передается конвейерным обра- зом. В некоторых системах командная строка
!
приведет к тому, что запустится две программы, otherprog и prog, и стан- дартный выход otherprog поступит на стандартный вход prog.
Функция int putchar(int)
используется для вывода:
отправляет символ с в стандарт-
ный вывод, под которым по умолчанию подразумевается экран. Функция в качестве результата посланный символ или, в слу- чае ошибки, EOF. To же и в отношении вывода: с помощью записи вида
> имя-файла вывод можно перенаправить в файл. Например, если prog использует для вывода функцию то prog > outfile будет направлять стандартный вывод не на экран, в
А команд- ная prog

и вывод
соединит стандартный вывод программы prog со стандартным вводом программы
Вывод, осуществляемый функцией также отправляется в стан- дартный выходной поток. Вызовы p u t c h a r и p r i n t f могут как угодно че- редоваться, при этом вывод будет формироваться в той последовательно- сти, в которой происходили вызовы этих функций.
Любой исходный Си-файл, использующий хотя бы одну функцию би- блиотеки ввода-вывода, должен содержать в себе строку

причем она должна быть расположена до первого обращения к вводу-вы- воду. Если имя заголовочного файла заключено в угловые скобки < и >,
это значит, что поиск заголовочного файла ведется в стандартном месте
(например в системе UNIX это обычно директорий /us
Многие программы читают только из входного потока и пишут только в один выходной поток. Для организации ввода-вывода таким про- граммам вполне хватит функций putchar и p r i n t f , а для началь- ного обучения уж точно достаточно ознакомления с этими функциями.
В частности, перечисленных функций достаточно, когда требуется вывод одной программы соединить с вводом следующей. В качестве примера рассмотрим программу переводящую свой ввод на нижний регистр:


/* lower: переводит ввод на нижний регистр */
{
int с;
while
=
EOF)
return 0;
}
Функция tolower определена в . Она переводит буквы верх- него регистра в буквы нижнего регистра, а остальные символы возвра- щает без изменений. Как мы уже упоминали, "функции" вроде и p u t c h a r из библиотеки и функция tolower из библиотеки
часто реализуются в виде макросов, чтобы исключить наклад- ные расходы от вызова функции на каждый отдельный символ. В пара- графе 8.5 мы покажем, как это делается. Независимо от того, как на той или иной машине реализованы функции библиотеки , использу- ющие их программы могут ничего не знать о кодировке символов.

7.2. Форматный вывод (printf)
Упражнение 7.1. Напишите осуществляющую перевод ввода с верхнего регистра на нижний или с нижнего на верхний в зависимости от имени, по которому она вызывается и текст которого находится в
7.2. Форматный вывод
Функция переводит внутренние значения в текст.
. . . )
В предыдущих главах мы использовали неформально. Здесь мы покажем наиболее типичные случаи применения этой функции; полное ее описание дано в приложении В.
Функция р rintf преобразует, форматирует и печатает свои аргументы в стандартном выводе под управлением формата. Возвращает она коли- чество напечатанных символов.
Форматная строка содержит два вида объектов: обычные символы, ко- торые впрямую копируются в выходной поток, и спецификации преобра- зования, каждая из которых вызывает преобразование и печать очеред- ного аргумента
Любая спецификация преобразования начина- ется знаком % и заканчивается символом-спецификатором. Между %
и символом-спецификатором могут быть расположены (в указанном ниже порядке) следующие элементы:
• Знак минус, предписывающий выравнивать преобразованный аргумент по левому краю поля.
• Число, специфицирующее минимальную ширину поля. Преобразован- ный аргумент будет занимать поле по крайней мере указанной шири- ны. При необходимости лишние позиции слева (или справа при лево- стороннем расположении) будут заполнены пробелами.
• Точка, отделяющая ширину поля от величины, устанавливающей точ- ность.
• Число (точность), специфицирующее максимальное количество печата- емых символов в строке, или количество цифр после десятичной точки - для чисел с плавающей запятой, или минимальное количество цифр - для целого.
• Буква h, если печатаемое целое должно рассматриваться как s h o r t ,
или 1 (латинская буква ell), если целое должно рассматриваться как long.
Символы-спецификаторы перечислены в в таблице
Если не поме- щен символ-спецификатор, поведение функции будет не определено.
Ширину и точность можно специфицировать с помощью *; значение ширины (или точности) в этом случае берется из следующего аргумента

Глава 7. Ввод и вывод
Таблица
Основные преобразования printf
Символ Тип аргумента;
вид печати
d, i int; десятичное целое о int; беззнаковое восьмеричное целое (без нуля слева)
х, X unsigned int;
целое или
ОХ слева), для используются abcdef или ABCOEF
десятичное целое с int; одиночный символ s c h a r *; печатает символы, расположенные до знака \0, или в количестве, заданном точностью f
[-]
где количество цифр d задается точно- стью (по умолчанию равно 6)
Е double; [-]
или [-]
количе- ство цифр d задается точностью (по умолчанию равно 6)
g, G double;
или больше или равен точности; в противном случае использу- ет
Завершающие нули и завершающая десятичная точ- ка не печатаются р void *; указатель (представление зависит от реализации)
% Аргумент не преобразуется; печатается знак %
(который должен быть типа int). Например, чтобы напечатать не более max символов из строки s, годится следующая запись:
max, s);
Большая часть форматных преобразований была продемонстрирована в предыдущих главах. Исключение составляет задание точности для строк.
Далее приводится перечень спецификаций и показывается их влияние на печать строки "hello,
состоящей из 12 символов. Поле специаль- но обрамлено двоеточиями, чтобы была видна его
:%s:
world:
:%10s: :hello, world:
wor:
:hello, world:
world:
:%-15s:
world :
: hello, wor:
:hello, wor

7.3. Списки аргументов переменной длины
Предостережение: функция printf использует свой первый аргумент,
чтобы определить, сколько еще ожидается аргументов и какого они будут типа. Вы не получите правильного результата, если аргументов будет не хватать или они будут принадлежать не тому типу. Вы должны также понимать разницу в следующих двух обращениях:
/* НЕВЕРНО, если в s есть %, */
s); /* ВЕРНО всегда */
Функция выполняет же преобразования, что и вы- вод запоминает в строке int char
. . . )
Эта функция форматирует т. д. в соответствии с информацией,
заданной аргументом format, как мы описывали ранее, но результат по- мещает не в стандартный вывод, а в string. Заметим, что строка должна быть достаточно большой, чтобы в ней поместился результат.
Упражнение 7.2. Напишите программу, которая будет печатать разумным способом любой ввод. Как минимум она должна уметь печатать негра- фические символы в восьмеричном виде (в форме,
принятой на вашей машине), обрывая длинные текстовые строки.
7.3. Списки аргументов переменной длины
Этот параграф содержит реализацию минимальной версии
При- водится она для того, чтобы показать, как надо писать функции со спис- ками аргументов переменной длины, причем такие, которые были бы пе- реносимы. Поскольку нас главным образом интересует обработка аргу- ментов, функцию m i n p r i n t f напишем таким образом, что она в основном будет работать с задающей формат строкой и аргументами; что же каса- ется форматных преобразований, то они будут осуществляться с помощью стандартного p r i n t f .
Объявление стандартной функции p r i n t f выглядит так:
int printf(char
...)
Многоточие в объявлении означает, что число и типы аргументов могут изменяться. Знак многоточие может стоять только в конце списка аргу- ментов. Наша функция m i n p r i n t f объявляется как void m i n p r i n t f ( c h a r
. . . )
поскольку она не будет выдавать число символов, как это делает

200 Глава 7. Ввод и вывод
Вся сложность в каким образом будет продвигаться вдоль списка аргументов, - ведь у этого списка нет даже имени. Стандартный заголовочный файл h> содержит набор макроопределений, кото- рые устанавливают, как шагать по списку аргументов. Наполнение этого заголовочного файла может изменяться от машины к машине, но пред- ставленный им интерфейс везде одинаков.
Тип служит для описания переменной, которая будет по очере- ди указывать на каждый из аргументов; в m i n p r i n t f эта переменная имеет имя ар (от "argument
- указатель на аргумент). Макрос инициализирует переменную чтобы она указывала на первый безы- мянный аргумент. К
нужно обратиться до первого использова- ния ар. Среди аргументов по крайней мере один должен быть именован- ным; от последнего именованного аргумента этот макрос "отталкивается"
при начальной установке.
Макрос на каждом своем вызове выдает очередной а ар передвигает на следующий; по имени типа он определяет тип воз- вращаемого значения и размер шага для выхода на следующий аргумент.
Наконец, макрос va_end делает очистку всего, что необходимо. К
следует обратиться перед самым выходом из функции.
Перечисленные средства образуют основу нашей упрощенной версии
/* minprintf: минимальный printf с переменным числом аргумент */
void
{
va_list ар;
указывает на очередной безымянный аргумент */
double va_start(ap, fmt); /*
ар на 1-й
*/
for (p = fmt; *р; р++) {
if (*p !=
{
continue;
}
switch (*++р) {
case ival =
int);
printf ival);

7.4.
ввод
201
break;
case ' :
dval = va_arg(ap, double);
dval);
break;
case for
= va_arg(ap, char *);
sval++)
putchar(*sval);
break;
break;
}
/* очистка, когда все сделано */
Упражнение 7.3. Дополните другими возможностями printf.
7.4. Форматный ввод
Функция обеспечивающая ввод, является аналогом она выполняет многие из упоминавшихся преобразований, но в противопо- ложном направлении. Ее объявление имеет следующий вид:
int scanf(char
...)
Функция читает символы из стандартного входного интер- претирует их согласно спецификациям строки и рассылает резуль- таты в свои остальные аргументы. Аргумент-формат мы опишем позже;
другие
каждый из которых должен
указателем, опреде- ляют, где будут запоминаться должным образом преобразованные дан- ные. Как и для p r i n t f , в этом параграфе дается сводка наиболее полез- но отнюдь не возможностей данной функции.
Функция прекращает работу, когда оказывается, что исчерпался формат или вводимая величина не соответствует управляющей специфи- кации. В качестве результата возвращает количество успешно вве- денных элементов данных. По исчерпании файла она выдает EOF. Суще- ственно что значение EOF не равно нулю, поскольку нуль выдает,
когда вводимый символ не соответствует первой спецификации формат- ной строки. Каждое очередное обращение к продолжает ввод с сим- вола, следующего сразу за последним обработанным.

202
Глава 7. Ввод и вывод
Существует также функция которая читает из строки из стан- дартного ввода).
int
*string, char
Функция sscanf просматривает строку string согласно формату format и рассылает полученные значения в и т. д. Последние должны быть указателями.
Формат обычно содержит спецификации, которые используются для управления преобразованиями ввода. В него могут входить следующие элементы:
Пробелы или табуляции, которые игнорируются.
• Обычные символы (исключая %), которые, как ожидается, совпадут с очередными символами, отличными от символов-разделителей вход- ного потока.
• Спецификации преобразования, каждая из которых начинается со зна- ка и завершается символом-спецификатором типа преобразования.
В промежутке между этими двумя символами в любой спецификации могут располагаться, причем том порядке,
они здесь указаны: знак *
(признак подавления присваивания); число, определяющее ширину поля; буква h, 1 или L, указывающая на размер получаемого значения;
и символ преобразования (о, x).
Спецификация преобразования управляет преобразованием следу- ющего вводимого поля. Обычно результат помещается в переменную,
на которую указывает соответствующий аргумент. Однако если в спе- цификации преобразования присутствует *, то поле ввода пропускается и никакое присваивание не выполняется. Поле ввода определяется как строка без символов-разделителей; оно простирается до следующего сим- вола-разделителя же ограничено шириной поля, если она задана. По- скольку символ новой строки относится к то scanf при чтении будет переходить с одной строки на другую. (Символа- ми-разделителями являются символы пробела, табуляции, новой строки,
возврата каретки, вертикальной табуляции и перевода страницы.)
Символ-спецификатор указывает, каким образом следует интерпрети- ровать очередное поле ввода. Соответствующий аргумент должен быть указателем, как того требует механизм передачи параметров по значению,
принятый в Си. Символы-спецификаторы приведены в таблице 7.2.
Перед о, и и х может стоять буква h, указывающая на то, что соответствующий аргумент должен иметь тип short *
не int *), или 1 (латинская ell), указывающая на тип long *. Ана- логично, перед символами-спецификаторами е, f и g может стоять бук- ва 1, указывающая, что тип аргумента - double * (а не

7.4. Форматный ввод
203
Таблица 7.2. Основные преобразования scanf
Символ • Вводимые данные;
тип аргумента
десятичное целое;
*
i целое; int *. Целое может восьмеричным (с 0 слева)
или
(с Ох или ОХ слева)
о восьмеричное целое (с нулем слева или без него); int *
и беззнаковое десятичное целое; unsigned х шестнадцатеричное целое (с Ох или ОХ слева или без них);
int *
с символы;
Следующие символы ввода (по умолчанию один) размещаются в указанном месте. Обычный пропуск символов-разделителей подавляется; чтобы прочесть оче- редной отличный от символа-разделителя, ис- пользуйте s
s строка символов (без обрамляющих кавычек);
указыва- ющая на массив символов, достаточный для строки и завер- шающего символа '
который будет добавлен
ё, f, g число с плавающей точкой, возможно, со знаком; обязательно присутствие либо десятичной точки, либо экспоненциаль- ной части, а возможно, и обеих вместе;
*
% сам знак %, никакое присваивание не выполняется
Чтобы построить первый пример, обратимся к программе калькулято- ра из главы 4, в которой организуем ввод с помощью функции
/* программа-калькулятор */
{
double sum,
sum
= 0;
while (scanf
&v) == 1)
sum += v);
return 0;
}
Предположим, что нам нужно прочитать строки ввода, содержащие данные вида
25
дек
1988

204 Глава 7. Ввод и
Обращение к scanf выглядит следующим образом:
day, year; /* день, год */
char
/* название месяца */
scanf
%s
&year); .
Знак & перед monthname не нужен, так как имя массива есть указатель.
В строке формата могут присутствовать символы, не участвующие ни в одной из спецификаций; это значит, что эти символы должны появить- ся на вводе. Так, мы могли бы читать даты вида mm/dd/yy с помощью сле- дующего обращения к int day, month, year; /* день, месяц, год */
&day, &month, &year);
В своем формате функция scanf игнорирует пробелы и табуляции. Кро- ме того, при поиске следующей порции ввода она пропускает во входном потоке все символы-разделители (пробелы,
новые строки и т. д.). Воспринимать входной поток, не имеющий фиксированного фор- мата, часто оказывается удобнее, если вводить всю строку целиком и для каждого отдельного случая подбирать подходящий вариант
Пред- положим, например, что нам нужно читать строки с датами, записан- ными в любой из приведенных выше форм. Тогда мы могли бы написать:
while (getline(line,
> 0) {
if
&day, monthname, &year) == 3)
line); /* в виде 25 дек 1988 */
else if (sscanf(line,
&day, &year) == 3)
line); /* в виде mm/dd/yy */
else line); /* неверная форма даты */
}
Обращения к scanf могут перемежаться с вызовами других функций ввода. Любая функция ввода, вызванная после продолжит чтение с первого еще непрочитанного символа.
В завершение еще раз напомним, что аргументы функций scanf и sscanf
должны быть указателями.
Одна из самых распространенных ошибок состоит в том, что вместо того, чтобы написать пишут n);
Компилятор о подобной ошибке ничего не сообщает.

7.5. Доступ к файлам 205
Упражнение 7.4. Напишите свою версию по аналогии с из предыдущего параграфа.
Упражнение 7.5. Перепишите основанную на постфиксной записи программу калькулятора из главы 4 таким образом, чтобы для ввода и преобразования чисел она использовала и/или
7.5. Доступ к файлам
Во всех предыдущих примерах мы имели дело со стандартным вводом и
выводом, которые для программы автоматически пред- определены операционной системой конкретной машины.
Следующий шаг - научиться писать которые имели бы доступ к файлам, заранее не подсоединенным к программам.
из про- грамм, в которой возникает такая необходимость, - это программа cat,
объединяющая несколько именованных файлов и направляющая резуль- тат в стандартный вывод. Функция cat часто применяется для выдачи файлов на экран, а также как универсальный "коллектор" файловой ин- формации для тех программ, которые не имеют возможности обратиться к файлу по имени. Например, команда cat направит в стандартный вывод содержимое файлов х. с и у. с (и ничего более).
Возникает вопрос: что надо сделать, чтобы именованные файлы мож- но было читать; иначе говоря, как связать внешние имена, придуманные пользователем, с инструкциями чтения данных?
На этот счет имеются простые правила. Для того чтобы можно было читать из файла или писать в файл, он должен быть предварительно от-
крыт с помощью библиотечной функции
Функция open получает внешнее имя типа х. с или с, после чего осуществляет некоторые орга- низационные действия и "переговоры" с операционной системой (техни- ческие детали которых здесь не рассматриваются) и возвращает указа- тель, используемый в дальнейшем для доступа к файлу.
Этот указатель, называемый указателем файла, ссылается на струк- туру, содержащую информацию о файле (адрес буфера, положение те- кущего символа в буфере, открыт файл на чтение или на запись, были ли ошибки при работе с файлом и не встретился ли конец файла).
Пользователю не нужно знать подробности, поскольку определения, по- лученные из h>, включают описание такой структуры,
мой FILE.

206
7. Ввод и вывод
что требуется для определения указателя файла, - это задать такого, например, вида:
FILE
FILE
char
Это говорит, что f p есть указатель на FILE, a f open возвращает указатель на FILE. Заметим, что имя типа, наподобие int,
тег структу- ры. Оно определено с помощью
(Детали того, как можно реали- зовать f open в системе UNIX, приводятся в параграфе 8.5.)
Обращение к f open в программе может выглядеть следующим образом:
fp =
mode);
Первый аргумент - строка, содержащая имя файла. Второй аргумент несет информацию
Это тоже строка: в ней указывается, каким образом пользователь намерен применять файл. Возможны следующие режимы:
чтение запись (write -
и добавление "а"), т. е.
запись информации в конец уже существующего файла. В некоторых си- стемах различаются текстовые и бинарные файлы; в случае последних в строку режима необходимо добавить букву
(binary - бинарный).
Тот факт, что некий файл, которого раньше не открывается на за- пись или добавление, означает, что он создается (если такая процедура физически возможна). Открытие уже существующего файла на запись приводит к выбрасыванию его старого содержимого, в то время как при открытии файла на добавление его старое содержимое сохраняется. По- пытка читать несуществующий файл является ошибкой. Могут иметь место и другие ошибки; например, ошибкой считается попытка чтения файла, который по статусу запрещено читать. При наличии любой ошиб- ки f возвращает NULL.
более точная идентификация ошиб- ки; детальная информация по этому поводу приводится в конце парагра- фа 1 приложения В.)
Следующее, что нам необходимо "знать, - это как читать из файла или писать в файл, коль скоро он открыт. Существует несколько способов сделать это, из которых самый простой состоит в том, чтобы воспользо- ваться функциями getc и putc. Функция getc возвращает следующий сим- вол из файла; ей необходимо сообщить указатель файла, чтобы она знала откуда брать символ.
int getc(FILE *fp)
Функция getc возвращает следующий символ из потока, на который ука- зывает в случае исчерпания файла или ошибки она возвращает EOF.
Функция putc пишет символ с в файл f p int putc(int с, FILE *fp)

Доступ к
и возвращает символ или EOF в случае ошибки. Аналогично и
реализация getc и putc может быть выполнена в виде макросов, а не функций.
При запуске Си-программы операционная система всегда открывает три файла и обеспечивает три файловые ссылки на них. Этими файлами являются: стандартный стандартный вывод и стандартный файл ошибок; соответствующие им указатели называются stdin,
и stderr;
они описаны в
Обычно stdin соотнесен с клавиатурой, a stdout и stderr - с экраном. Однако stdin и stdout можно связать с файлами или, используя конвейерный механизм, соединить напрямую с другими как это описывалось в параграфе
С помощью getc, putc, stdin и stdout функции getchar и p u t c h a r теперь можно определить следующим образом:
fldefine getc(stdin)
tfdefine stdout)
Форматный ввод-вывод файлов можно построить на функциях
Они идентичны и printf лишь разницей, что первым их аргументом является указатель на файл, для которого осуществляется формат же указывается вторым аргументом.
int fscanf(FILE
char
...)
int fprintf(FILE
char
...)
Вот теперь мы располагаем теми сведениями, которые достаточны для написания программы cat, предназначенной для конкатенации (после- довательного соединения) файлов. Предлагаемая версия функции cat, как оказалось, удобна для многих программ. Если в командной строке при- сутствуют аргументы, они рассматриваются как имена последовательно обрабатываемых файлов. Если аргументов нет, то обработке подвергает- ся стандартный ввод.
ttinclude
/*
конкатенация файлов, версия 1 */
main(int argc, char
{
FILE *fp;
void
*, FILE *);
if (argc
/* нет аргументов; копируется стандартный ввод */
filecopy(stdin,
else while
> 0)

208 _ _ Глава 7. Ввод и вывод
if ((fp = fopen(*++argv,
== NULL) {
не могу открыть файл return
} else {
filecopy(fp, stdout);
>
return 0;
/* filecopy: копирует файл в файл
*/
void filecopy(FILE *ifp, FILE *ofp)
{
int c;
while
=
EOF)
ofp);
}
Файловые указатели stdin и stdout представляют собой объекты типа FILE*.
Это константы, а не переменные, следовательно, им нельзя ничего при- сваивать.
Функция int fclose(FILE *fp)
- обратная по отношению к f open; она разрывает связь между файловым указателем и внешним именем (которая раньше была установлена с по- мощью f open), освобождая тем самым этот указатель для других файлов.
Так как в большинстве операционных систем количество одновременно открытых одной программой файлов ограничено, то файловые указатели,
если они больше не нужны, лучше освобождать, как это и делается в грамме cat. Есть еще одна причина применить f к файлу вывода,
это необходимость "опорожнить" буфер, в котором putc накопила пред- назначенные для вывода данные.
нормальном завершении работы программы для каждого открытого файла f close вызывается автомати- чески. (Вы можете закрыть stdin и stdout, если они вам не нужны.
пользовавшись библиотечной функцией f reopen, их можно
7.6. Управление ошибками
и exit)
Обработку ошибок в cat нельзя признать идеальной. Беда в том, что если файл по какой-либо сообщение об этом мы по- лучим по окончании конкатенируемого вывода. Это нас устроило бы, если

7.6. Управление ошибками
и exit) _ 209
бы вывод отправлялся только на экран, а не в файл или по конвейеру дру- гой программе.
Чтобы лучше справиться с этой программе помимо стан- дартного вывода stdout придается еще один выходной поток, называемый stderr. Вывод в stderr обычно отправляется на даже если вывод stdout перенаправлен в другое место.
Перепишем cat так, чтобы сообщения об ошибках отправлялись в stde r г.
/*
конкатенация файлов, версия 2 */ .
argc, char
{
FILE *fp;
void
*, FILE *);
char *prog =
/* имя программы */
if (argc
/* нет аргументов; копируется станд. ввод */
filecopy(stdin, stdout);
else while
> 0)
if ((fp =
== NULL) {
не могу открыть файл prog, *argv);
} else {
filecopy(fp, stdout);
}
if
{ .
ошибка записи в prog);
exit(2);
}
exit(O);
Программа сигнализирует об ошибках двумя способами. Первый - со- общение об ошибке с помощью посылается в stderr с тем, чтобы оно попало на экран, а не оказалось на конвейере или в другом файле вы- вода. Имя программы, хранящееся в мы включили в сообщение,
чтобы в слу/чаях, когда данная программа работает совместно с другими,
был ясен источник ошибки.

210 Глава 7. Ввод и вывод
Второй способ указать на ошибку — обратиться к библиотечной функ- ции exit, завершающей работу программы. Аргумент функции exit до- ступен некоторому процессу, вызвавшему данный процесс. А следователь- но, успешное или ошибочное завершение программы можно проконтро- лировать с помощью некоей программы, которая рассматривает эту программу в качестве подчиненного процесса. По общей договоренности возврат нуля сигнализирует о том, что работа прошла нормально, в то время как ненулевые значения обычно говорят об ошибках. Чтобы опо- рожнить буфера, накопившие информацию для всех открытых файлов вывода, функция exit вызывает
Инструкция return главной программы main эквивалентна обра- щению к функции
Последний вариант (с помощью exit) име- ет то преимущество, что он пригоден для выхода и из других функций,
и, кроме того, слово exit легко обнаружить с помощью программы кон- текстного поиска, похожей на ту, которую мы рассматривали в главе 5.
Функция выдает ненулевое значение, если в файле fp была об- наружена ошибка.
int ferror(FILE *fp)
Хотя при выводе редко возникают ошибки, все же они встречаются (на- пример, оказался переполненным диск); поэтому в программах широко- го пользования они должны тщательно контролироваться.
Функция feof аналогична функции она возвращает не- нулевое значение, если встретился конец указанного в аргументе файла.
int feof(FILE
В наших небольших иллюстративных программах мы не заботились о выдаче статуса выхода, т. е. выдаче некоторого числа, характеризующего состояние программы в момент завершения: работа закончилась нормаль- но или прервана из-за ошибки? Если работа прервана в результате ошиб- ки, то какой? Любая серьезная программа должна выдавать статус выхода.
7.7. Ввод-вывод строк
В стандартной библиотеке имеется программа ввода f gets, аналогич- ная программе get которой мы пользовались в предыдущих главах.
char *fgets(char int maxline, FILE *fp)
Функция f gets читает следующую строку ввода (включая и символ но- вой строки) из файла f p в массив символов причем она может про- читать не более символов. Переписанная строка дополняется

7.7. Ввод-вывод строк _ _
символом
Обычно f gets возвращает line, а по исчерпании файла или в случае ошибки - NULL. (Наша getline возвращала длину строки, кото- рой мы потом пользовались, и нуль в случае конца файла.)
Функция вывода f puts пишет строку (которая может и не заканчиваться символом новой строки) в файл.
int fputs(char *line, FILE *fp)
Эта функция возвращает EOF, если возникла ошибка, и неотрицательное значение в противном случае.
Библиотечные функции gets и puts подобны функциям f gets и f puts.
Отличаются они тем, что оперируют только стандартными файлами и
и кроме того, gets выбрасывает последний символ '
a puts его добавляет.
Чтобы показать, что ничего особенного в функциях вроде f gets и f puts нет, мы приводим их здесь в том виде, в каком они существуют в стандарт- ной библиотеке на нашей системе.
/*
получает не более n символов из iop */
*s, int n, FILE *iop)
{
register int c;
register char *cs;
cs = s;
while
> 0 && (c =
EOF)
if
= c) ==
)
break;
=
return (c == EOF && cs == s) ? NULL : s;
/*
посылает строку s в файл iop */
int
*s, FILE *iop)
{
int c;
while (c = *s++)
putc(c, iop);
return ferror(iop) ? EOF : 0;
}
Стандарт определяет, что функция возвращает в случае ошибки ненулевое значение;
в случае ошибки возвращает EOF, в противном случае - неотрицательное значение.

212 __
Ввод и вывод
С помощью fgets легко реализовать нашу функцию getline:
/* getline: читает строку, возвращает ее длину */
int
*line, int max)
{
if (fgets(line, max, stdin) == NULL)
return 0;
else return strlen(line);
Упражнение 7.6. Напишите программу, сравнивающую два файла и пе- чатающую первую строку, в которой они различаются. •
Упражнение 7.7. Модифицируйте программу поиска по образцу из гла- вы 5 таким образом, чтобы она брала текст из множества именованных файлов, а если имен файлов в аргументах нет, то из стандартного ввода.
Будет ли печататься имя файла, в котором найдена подходящая строка?
Упражнение 7.8. Напишите программу, печатающую несколько файлов.
Каждый файл должен начинаться с новой страницы, предваряться заголовком и иметь свою нумерацию страниц.
7.8. Другие библиотечные функции
В стандартной библиотеке представлен широкий спектр различных функ- ций. Настоящий параграф содержит краткий обзор наиболее полезных из них. Более подробно эти и другие функции описаны в приложении В.
Операции со строками
Мы уже упоминали функции strcpy, strcat и описание которых даны в Далее, до конца пункта, предполагается, что s и t имеют тип char с и п - тип int.
- приписывает t в конец s.
- приписывает п символов из t в конец s.
st
- возвращает отрицательное число, нуль или положительное число для s < t, s == t или s > t соответственно.
- делает же, что и strcmp, но количество срав- ниваемых символов не может превышать п.
- копирует t в s.

7.8. Другие библиотечные функции 213
t, n) - копирует не более п символов из t в s.
- возвращает длину s.
st rch
- возвращает указатель на первое появление символа с в s или, если с нет в s, NULL.
с) - возвращает указатель на последнее появление символа с в s или, если с нет в s, NULL.
7.8.2. Анализ класса символов
и преобразование символов
Несколько функций из библиотеки выполняют проверки и преобразование символов. Далее, до конца пункта, переменная с - это переменная типа int, которая может быть представлена значением unsigned или EOF. Все эти функции возвращают значения типа int.
isalpha(c) - не нуль, если с - буква; 0 в противном isupper(c) - не нуль, если с - буква верхнего
О в противном
- не нуль, если с - буква нижнего регистра;
О в противном случае.
isdigit(c) - не нуль, если с - цифра; 0 в противном случае.
- isdigit(c)
истинны; 0 в противном isspace(c) - не нуль, если с - символ пробела, табуляции,
новой строки, возврата каретки,
перевода страницы, вертикальной табуляции.
- возвращает с, приведенную к верхнему регистру.
tolowe r( - возвращает с, приведенную к нижнему регистру.
7.8.3. Функция ungetc
В стандартной библиотеке содержится более ограниченная версия функции ungetch по сравнению с той, которую мы написали в главе 4. На- зывается она ungetc. Эта функция, имеющая прототип int ungetc(int с, FILE
отправляет символ с назад в файл f p и возвращает с, а в случае ошибки
EOF. Для каждого файла гарантирован возврат не более одного символа.
Функцию ungetc можно использовать совместно с любой из функций ввода вроде getc, getchar и т. д.
7.8.4. Исполнение команд операционной системы
Функция s y s t e m ( c h a r *s) выполняет команду системы, содержащую- ся в строке s, и затем возвращается к выполнению текущей программы.

214 Глава 7. Ввод и вывод
Содержимое s,
говоря, зависит от конкретной операционной си- стемы. Рассмотрим простой пример: в системе UNIX инструкция вызовет программу date, которая направит дату и время в стандартный вывод. Функция возвращает зависящий от системы статус выполненной команды. В системе UNIX возвращаемый статус - это значение, передан- ное функцией exit.
7.8.5. Управление памятью
Функции и calloc динамически запрашивают блоки свободной памяти. Функция void n)
возвращает указатель на n байт неинициализированной памяти или NULL,
если запрос удовлетворить нельзя. Функция calloc void *calloc(size_t n, size_t size)
возвращает указатель на область, достаточную для хранения массива из n объектов указанного размера (size), или NULL, если запрос не удается удов- летворить. Выделенная память обнуляется.
Указатель, возвращаемый функциями malloc и calloc, будет выдан с уче- том выравнивания, выполненного согласно указанному типу объекта. Тем не менее к нему должна быть применена операция приведения к соответ- ствующему как это сделано в следующем фрагменте программы:
int *ip;
ip = (int *) calloc(n,
Функция f освобождает область памяти, на которую указывает р, - указатель, первоначально полученный с помощью malloc или calloc. Ни- каких ограничений на порядок, в котором будет освобождаться память,
нет, но считается ужасной ошибкой освобождение тех областей, которые не были получены с помощью calloc или malloc. •
Нельзя также использовать те области памяти, которые уже освобож- дены. Следующий пример демонстрирует типичную ошибку в цикле,
освобождающем элементы списка.
(р = head; р != NULL; р = p->next)
НЕВЕРНО */
Правильным будет, если вы до освобождения сохраните то, что вам по-
' Как
(см.
па с.
о возвращаемых функциями m a l l o c или
- Примеч. акт.

7.8. Другие
функции 215
требуется, как в следующем цикле:
for (p head; p != NULL; p = q) {
q = p->next;
}
В параграфе 8.7 мы рассмотрим реализацию программы управления па- мятью вроде позволяющую освобождать выделенные блоки па- мяти в любой последовательности.
7.8.6. Математические функции
В
h> описано более двадцати математических функций. Здесь же приведены наиболее употребительные. Каждая из них имеет один или два аргумента типа double и возвращает результат также типа double.
- синус х, х в радианах.
cos(x) -
х, х в радианах.
- арктангенс радианах.
ехр(х) - экспоненциальная функция е*.
log(x) - натуральный (по
е)
х
loglO(x) - обычный (по основанию 10) логарифм х (х > 0).
- корень квадратный х (х > 0).
fabs(x) - абсолютное значение
7.8.7. Генератор случайных чисел
Функция rand вычисляет последовательность псевдослучайных це- лых в диапазоне от нуля до значения, заданного именованной констан- той которая определена в
Привести случайные чис- ла к значениям с плавающей точкой, большим или равным 0 и меньшим 1,
можно по формуле
«define rand() /
(Если в вашей библиотеке уже есть функция для получения случайных чисел с плавающей точкой, вполне возможно, что ее статистические ха- рактеристики лучше указанной.)
Функция устанавливает семя для rand. Реализации rand и
предлагаемые стандартом и, следовательно, переносимые на раз- личные машины, рассмотрены в параграфе 2.7.
Упражнение 7.9. Реализуя функции вроде isupper, можно экономить либо память, либо время. Напишите оба варианта функции.

1   ...   11   12   13   14   15   16   17   18   ...   28


написать администратору сайта