Мы также писали 32.0 вместо 32 , несмотря на то, что так как переменная FAHR
имеет тип FLOAT , целое 32 автоматически бы преобразовалось к типу FLOAT ( в 32.0)
перед вычитанием. С точки зрения стиля разумно писать плавающие константы с явной
десятичной точкой даже тогда, когда они имеют целые значения; это подчеркивает их
плавающую природу для просмат- ривающего программу и обеспечивает то, что компилятор
будет смотреть на вещи так же, как и Вы.
Подробные правила о том, в каком случае целые преобразу- ются к типу с плаваюшей
точкой, приведены в главе 2. Сейчас же отметим, что присваивание
FAHR = LOWER;
проверка
WHILE (FAHR <= UPPER)
работают, как ожидается, - перед выполнением операций целые преобразуются в плавающую
форму.
Этот же пример сообщает чуть больше о том, как работает PRINTF. Функция PRINTF
фактически является универсальной функцией форматных преобразований, которая будет
полностью описана в главе 7. Ее первым аргументом является строка сим- волов, которая
должна быть напечатана, причем каждый знак % указывает, куда должен подставляться
каждый из остальных ар- гументов /второй, третий, .../ и в какой форме он должен
пе- чататься. Например, в операторе
PRINTF("%4.0F %6.1F\N", FAHR, CELSIUS);
спецификация преобразования %4.0F говорит, что число с пла- вающей точкой должно
быть напечатано в поле шириной по край- ней мере в четыре символа без цифр после
десятичной точки. спецификация %6.1F описывает другое число, которое должно занимать
по крайней мере шесть позиций с одной цифрой после десятичной точки, аналогично
спецификациям F6.1 в фортране или F(6,1) в PL/1. Различные части спецификации могут
быть опущены: спецификация %6F говорит, что число будет шириной по крайней мере
в шесть символов; спецификация %2 требует двух позиций после десятичной точки, но
ширина при этом не ограничивается; спецификация %F говорит только о том, что нужно
напечатать число с плавающей точкой. Функция PRINTF также распознает следующие спецификации:
%D - для десятично- го целого, %о - для восьмеричного числа, %х - для шестнадца-
тиричного, %с - для символа, %S - для символьной строки и %% - для самого символа
%.
Каждая конструкция с символом % в первом аргументе функ- ции PRINTF сочетается
с соответствующим вторым, третьим, и т.д. Аргументами; они должны согласовываться
по числу и ти- пу; в противном случае вы получите бессмысленные результаты.
Между прочим, функция PRINTF не является частью языка "C"; в самом языке "C"
не определены операции ввода-вывода. Нет ничего таинственного и в функции PRINTF
; это - просто полезная функция, являющаяся частью стандартной библиотеки подпрограмм,
которая обычно доступна "C"-программам. Чтобы сосредоточиться на самом языке, мы
не будем подробно оста- навливаться на операциях ввода-вывода до главы 7. В частнос-
ти, мы до тех пор отложим форматный ввод. Если вам надо ввести числа - прочитайте
описание функции SCANF в главе 7, раздел 7.4. Функция SCANF во многом сходна с PRINTF
, но она считывает входные данные, а не печатает выходные.
Упражнение 1-3
Преобразуйте программу перевода температур таким обра- зом, чтобы она печатала
заголовок к таблице.
Упражнение 1-4
Напишите программы печати соответствующей таблицы пере- хода от градусов цельсия
к градусам фаренгейта.
1.3. Оператор FOR
Как и можно было ожидать, имеется множество различных способов написания каждой
программы. Давайте рассмотрим та- кой вариант программы перевода температур:
MAIN() /* FAHRENHEIT-CELSIUS TABLE */ {
INT FAHR;
FOR (FAHR = 0; FAHR <= 300; FAHR = FAHR + 20)
PRINTF("%4D %6.1F\N", FAHR, (5.0/9.0)*(FAHR-32.0)); }
Эта программа выдает те же самые результаты, но выглядит безусловно по-другому.
Главное изменение - исключение боль- шинства переменных; осталась только переменная
FAHR , причем типа INT (это сделано для того, чтобы продемонстрировать преобразование
%D в функции PRINTF). Нижняя и верхняя грани- цы и размер щага появляются только
как константы в операторе FOR , который сам является новой конструкцией, а выражение,
вычисляющее температуру по цельсию, входит теперь в виде третьего аргумента функции
PRINTF , а не в виде отдельного оператора присваивания.
Последнее изменение является примером вполне общего пра- вила языка "C" - в любом
контексте, в котором допускается использование значения переменной некоторого типа,
вы можете использовать выражение этого типа. Так как третий аргумент функции PRINTF
должен иметь значение с плавающей точкой, чтобы соответствовать спецификации %6.1F,
то в этом месте может встретиться любое выражение плавающего типа.
Сам оператор FOR - это оператор цикла, обобщающий опера- тор WHILE. Его функционирование
должно стать ясным, если вы сравните его с ранее описанным оператором WHILE . Оператор
FOR содержит три части, разделяемые точкой с запятой. Первая часть
FAHR = 0
выполняется один раз перед входом в сам цикл. Вторая часть - проверка, или условие,
которое управляет циклом:
FAHR <= 300
это условие проверяется и, если оно истинно, то выполняется тело цикла (в данном
случае только функция PRINTF ). Затем выполняется шаг реинициализации FAHR =FAHR
+ 20
и условие проверяется снова. цикл завершается, когда это ус- ловие становится
ложным. Так же, как и в случае оператора WHILE , тело цикла может состоять из одного
оператора или из группы операторов, заключенных в фигурные скобки. Инициали- зирующая
и реинициализирующая части могут быть любыми от- дельными выражениями.
Выбор между операторами WHILE и FOR произволен и основы- вается на том , что
выглядит яснее. Оператор FOR обычно удо- бен для циклов, в которых инициализация
и реинициализация логически связаны и каждая задается одним оператором, так как
в этом случае запись более компактна, чем при использо- вании оператора WHILE ,
а операторы управления циклом сосре- дотачиваются вместе в одном месте.
Упражнение 1-5
Модифицируйте программу перевода температур таким обра- зом, чтобы она печатала
таблицу в обратном порядке, т.е. От 300 градусов до 0.
1.4. Символические константы
Последнее замечание, прежде чем мы навсегда оставим программу перевода температур.
Прятать "магические числа", такие как 300 и 20, внутрь программы - это неудачная
практи- ка; они дают мало информации тем, кто, возможно, должен бу- дет разбираться
в этой программе позднее, и их трудно изме- нять систематическим образом. К счастью
в языке "C" предус- мотрен способ, позволяющий избежать таких "магических чи- сел".
Используя конструкцию #DEFINE , вы можете в начале программы определить символическое
имя или символическую константу, которая будет конкретной строкой символов. Впос-
ледствии компилятор заменит все не заключенные в кавычки по- явления этого имени
на соответствующую строку. Фактически это имя может быть заменено абсолютно произвольным
текстом, не обязательно цифрами
#DEFINE LOWER 0/* LOWER LIMIT OF TABLE */
#DEFINE UPPER 300 /* UPPER LIMIT */
#DEFINE STEP 20 /* STEP SIZE */
MAIN () /* FAHRENHEIT-CELSIUS TABLE */
{
INT FAHR; FOR (FAHR =LOWER; FAHR <= UPPER; FAHR =FAHR + STEP)
PRINTF("%4D %6.1F\N", FAHR, (5.0/9.0)*(FAHR-32));
}
величины LOWER, UPPER и STEP являются константами и поэ- тому они не указываются
в описаниях. Символические имена обычно пишут прописными буквами, чтобы их было
легко отли- чить от написанных строчными буквами имен переменных. отме- тим, что
в конце определения не ставится точка с запятой. Так как подставляется вся строка,
следующая за определенным именем, то это привело бы к слишком большому числу точек
с запятой в операторе FOR .
1.5. Набор полезных программ
Теперь мы собираемся рассмотреть семейство родственных программ, предназначенных
для выполнения простых операций над символьными данными. В дальнейшем вы обнаружите,
что многие программы являются просто расширенными версиями тех прототипов, которые
мы здесь обсуждаем.
1.5.1. Ввод и вывод символов
Стандартная библиотека включает функции для чтения и за- писи по одному символу
за один раз. функция GETCHAR() извле- кает следующий вводимый символ каждый раз,
как к ней обраща- ются, и возвращает этот символ в качестве своего значения. Это
значит, что после
C = GETCHAR()
переменная 'C' содержит следующий символ из входных данных. Символы обычно поступают
с терминала, но это не должно нас касаться до главы 7.
Функция PUTCHAR(C) является дополнением к GETCHAR : в результате обращения
PUTCHAR (C)
содержимое переменной 'C' выдается на некоторый выходной но- ситель, обычно опять
на терминал. Обращение к функциям PUTCHAR и PRINTF могут перемежаться; выдача будет
появляться в том порядке, в котором происходят обращения.
Как и функция PRINTF , функции GETCHAR и PUTCHAR не со- держат ничего экстраординарного.
Они не входят в состав язы- ка "C", но к ним всегда можно обратиться.
1.5.2. Копирование файла
Имея в своем распоряжении только функции GETCHAR и PUTCHAR вы можете, не зная
ничего более об операциях вво- да-вывода, написать удивительное количество полезных
прог- рамм. Простейшим примером может служить программа посимволь- ного копирования
вводного файла в выводной. Общая схема име- ет вид: ввести символ WHILE (символ
не является признаком конца файла)
вывести только что прочитанный символ
ввести новый символ
программа, написанная на языке "C", выглядит следующим обра- зом:
MAIN() /* COPY INPUT TO OUTPUT; 1ST VERSION */
{
INT C;
C = GETCHAR();
WHILE (C != EOF) {
PUTCHAR (C);
C = GETCHAR();
}
}
оператор отношения != означает "не равно".
Основная проблема заключается в том, чтобы зафиксиро- вать конец файла ввода.
Обычно, когда функция GETCHAR натал- кивается на конец файла ввода, она возвращает
значение , не являющееся действительным символом; таким образом, программа может
установить, что файл ввода исчерпан. Единственное ос- ложнение, являющееся значительным
неудобством, заключается в существовании двух общеупотребительных соглашений о том,
ка- кое значение фактически является признаком конца файла. Мы отсрочим решение
этого вопроса, использовав символическое имя EOF для этого значения, каким бы оно
ни было. На практи- ке EOF будет либо -1, либо 0, так что для правильной работы
перед программой должно стоять собственно либо
#DEFINE EOF -1
либо
#DEFINE EOF 0
Использовав символическую константу EOF для представле- ния значения, возвращаемого
функцией GETCHAR при выходе на конец файла, мы обеспечили, что только одна величина
в прог- рамме зависит от конкретного численного значения.
Мы также описали переменную 'C' как INT , а не CHAR , с тем чтобы она могла хранить
значение, возвращаемое GETCHAR . как мы увидим в главе 2, эта величина действительно
INT, так как она должна быть в состоянии в дополнение ко всем возмож- ным символам
представлять и EOF.
Программистом, имеющим опыт работы на "C", программа копирования была бы написана
более сжато. В языке "C" любое присваивание, такое как
C = GETCHAR()
может быть использовано в выражении; его значение - просто значение, присваиваемое
левой части. Если присваивание сим- вола переменной 'C' поместить внутрь проверочной
части опе- ратора WHILE , то программа копирования файла запишется в виде:
MAIN() /* COPY INPUT TO OUTPUT; 2ND VERSION */ { INT C;
WHILE ((C = GETCHAR()) != EOF) PUTCHAR(C); }
Программа извлекает символ , присваивает его переменной 'C' и затем проверяет,
не является ли этот символ признаком конца файла. Если нет - выполняется тело оператора
WHILE, выводящее этот символ. Затем цикл WHILE повторяется. когда, наконец, будет
достигнут конец файла ввода, оператор WHILE завершается, а вместе с ним заканчивается
выполнение и функ- ции MAIN .
В этой версии централизуется ввод - в программе только одно обращение к функции
GETCHAR - и ужимается программа. Вложение присваивания в проверяемое условие - это
одно из тех мест языка "C", которое приводит к значительному сокра- щению программ.
Однако, на этом пути можно увлечься и начать писать недоступные для понимания программы.
Эту тенденцию мы будем пытаться сдерживать.
Важно понять , что круглые скобки вокруг присваивания в условном выражении действительно
необходимы. Старшинство операции != выше, чем операции присваивания =, а это означа-
ет, что в отсутствие круглых скобок проверка условия != бу- дет выполнена до присваивания
=. Таким образом, оператор
C = GETCHAR() != EOF
эквивалентен оператору
C = (GETCHAR() != EOF)
Это, вопреки нашему желанию, приведет к тому, что 'C' будет принимать значение
0 или 1 в зависимости от того, на- толкнется или нет GETCHAR на признак конца файла.
Подробнее об этом будет сказано в главе 2/.
1.5.3. Подсчет символов
Следующая программа подсчитывает число символов; она представляет собой небольшое
развитие программы копирования.
MAIN() /* COUNT CHARACTERS IN INPUT */
{
LONG NC;
NC = 0;
WHILE (GETCHAR() != EOF )
++NC;
PRINTF("%1D\N", NC);
}
Оператор
++NC;
демонстрирует новую операцию, ++, которая означает увеличе- ние на единицу. Вы
могли бы написать NC = NC + 1 , но ++NC более кратко и зачастую более эффективно.
Имеется соответст- вующая операция -- уменьшение на единицу. Операции ++ и -- могут
быть либо префиксными (++NC), либо постфиксными (NC++); эти две формы, как будет
показано в главе 2, имеют в выражениях различные значения, но как ++NC, так и NC++
уве- личивают NC. Пока мы будем придерживаться префиксных опера- ций.
Программа подсчета символов накапливает их количество в переменной типа LONG,
а не INT . На PDP-11 максимальное зна- чение равно 32767, и если описать счетчик
как INT , то он будет переполняться даже при сравнительно малом файле ввода; на
языке "C" для HONEYWELL и IBM типы LONG и INT являются синонимами и имеют значительно
больший размер. Спецификация преобразования %1D указывает PRINTF , что соответствующий
аргумент является целым типа LONG .