Упражнение 2-3. Преобразуйте программу перевода температур таким образом, чтобы она печатала заголовок к таблице.
Упражнение 2-4. Напишите программы печати соответствующей таблицы перехода от градусов Цельсия к градусам Фаренгейта.
Пример 2-3. Как и можно было ожидать, имеется множество различных способов написания каждой программы. Давайте рассмотрим такой вариант программы перевода температур:
main() // Напечатать таблицу Фаренгейт-Цельсий
{
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 , а операторы управления циклом сосредотачиваются вместе в одном месте.
Упражнение 2-5. Модифицируйте программу перевода температур таким образом, чтобы она печатала таблицу в обратном порядке, т.е. От 300 градусов до 0.
Последнее замечание, прежде чем мы навсегда оставим программу перевода температур. Прятать «магические числа», такие как 300 и 20, внутрь программы – это неудачная практика; они дают мало информации тем, кто, возможно, должен будет разбираться в этой программе позднее, и их трудно изменять систематическим образом. К счастью в языке «C» предусмотрены два способа, позволяющие избежать таких «магических чисел».
Первый способ. Используя конструкцию #define , вы можете в начале программы определить символическое имя или символическую константу, которая будет конкретной строкой символов. Впоследствии компилятор заменит все не заключенные в кавычки появления этого имени на соответствующую строку. Фактически это имя может быть заменено абсолютно произвольным текстом, не обязательно цифрами:
#define lower 0 // Нижний предел таблицы
#define upper 300 // Верхний предел таблицы
#define step 20 // Размер шага
main () // Напечатать таблицу Фаренгейт-Цельсий
{
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 .
Второй способ. После появления стандарта ANSI на объектно-ориентированное программирование в языке «C++» появился новый спецификатор для задания констант – const. В «C++» он может использоваться вместо конструкции #define.
Значения, которые были объявлены как const, не могут быть изменены в программе. Ниже приводятся некоторые примеры объявлений с использованием спецификатора const:
const int true = 1;
const double pl = 4*atan(1.0);
const int size = 5000;
char const *name1 = "Ричард С. Вайнер";
const char *name2 = "Лэвис Дж. Пинсон";
Четвертое объявление указывает, что name – это константный указатель на char, т.е. это адрес не может быть изменен.
Последнее объявление указывает, что строка «Лэвис Дж. Пинсон» является константой. Указатель name2 константой не является и может быть затем изменен.
Теперь мы собираемся рассмотреть семейство родственных программ, предназначенных для выполнения простых операций над символьными данными. В дальнейшем вы обнаружите, что многие программы являются просто расширенными версиями тех прототипов, которые мы здесь обсуждаем.
2.5.1. Ввод и вывод символов. Стандартная библиотека включает функции для чтения и записи по одному символу за один раз. функция getchar() извлекает следующий вводимый символ каждый раз, как к ней обращаются, и возвращает этот символ в качестве своего значения.
Это значит, что после c=getchar() переменная c содержит следующий символ из входных данных. Символы обычно поступают с терминала, но это не должно нас касаться до главы 9.
Функция putchar(c) является дополнением к getchar: в результате обращения putchar(c) содержимое переменной c выдается на некоторый выходной носитель, обычно опять на терминал. Обращение к функциям putchar и printf могут перемежаться; выдача будет появляться в том порядке, в котором происходят обращения.
Как и функция printf, функции getchar и putchar не содержат ничего экстраординарного. Они не входят в состав языка «C», но к ним всегда можно обратиться.
2.5.2. Копирование файла. Имея в своем распоряжении только функции getchar и putchar вы можете, не зная ничего более об операциях ввода-вывода, написать удивительное количество полезных программ.
Пример 2-4. Простейшим примером может служить программа посимвольного копирования вводного файла в выводной. Общая схема имеет вид:
ввести символ;
while (символ не является признаком конца файла)
{
вывести только что прочитанный символ;
ввести новый символ;
}
Программа, написанная на языке «C», выглядит следующим образом:
main() // Копировать ввод-вывод; 1-я версия
{
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. Как мы увидим в главе 3, эта величина действительно int, так как она должна быть в состоянии в дополнение ко всем возможным символам представлять и eof.
Программистом, имеющим опыт работы на «C», программа копирования была бы написана более сжато. В языке «C» любое присваивание, такое как
c = getchar()
может быть использовано в выражении; его значение – просто значение, присваиваемое левой части.
Пример 2-5. Если присваивание символа переменной c поместить внутрь проверочной части оператора while , то программа копирования файла запишется в виде:
main() // Копировать ввод-вывод; 2-я версия
{
int c;
while ((c = getchar()) != eof)
putchar(c);
}
Программа извлекает символ, присваивает его переменной c, и затем проверяет, не является ли этот символ признаком конца файла. Если нет – выполняется тело оператора while, выводящее этот символ. Затем цикл while повторяется. Когда, наконец, будет достигнут конец файла ввода, оператор while завершается, а вместе с ним заканчивается выполнение и функции main .
В этой версии централизуется ввод – в программе только одно обращение к функции getchar – и ужимается программа. Вложение присваивания в проверяемое условие – это одно из тех мест языка «C», которое приводит к значительному сокращению программ. Однако, на этом пути можно увлечься и начать писать недоступные для понимания программы. Эту тенденцию мы будем пытаться сдерживать.