Язык Си - руководство для начинающих - M. УЭИТ
Шрифт:
Интервал:
Закладка:
(ch = getchar( )) ! = STOP
имеет то же действие, что и
ch != STOP
Тем самым выполняется проверка, которую в программе ввод-вывод2 осуществлял оператор, расположенный в строке 10. Конструкции подобного сорта (объединение в одном выражении операций присваивания и сравнения) довольно часто используются при программировании на языке Си:
Аналогично нашему предыдущему примеру, в котором применялась конструкция while (++ size < 18.5), данная форма записи обладает тем преимуществом, что позволяет объединять в одном выражении проверку условия окончания цикла и действие по изменению одного из операндов операции сравнения. Подобная структура очень напоминает нам рассуждения, которыми мог бы сопровождаться данный процесс: "Я читаю символ, анализирую его и решаю, что делать дальше".
Теперь вернемся к нашей программе и попробуем ее выполнить. Если в вашей системе реализован небуферизованный ввод, результат может выглядеть, например, следующим образом:
ИИннттеерреесснноо ppаaббooттaаеeтт ллии ооннаа . Думаю что да.
При вводе все символы вплоть до признака STOP (звездочка), медленно отображаются на экране (эхо-печать). Дублируются даже пробелы. Однако, как только вы ввели признак STOP, работа программы прекращается и все, что вы набираете на пульте после этого, появляется на экране без эхо-дублирования.
Теперь посмотрим, что будет происходить в системе, обладающей буферизованным вводом. В этом случае программа не начнет работать до тех пор, пока вы не нажмете на клавишу [ввод]. Вот пример возможного диалога
Интересно, работает ли она. Гм , не знаю [ввод].
Интересно, работает ли она.
Первая строка была целиком передана программе. Программа последовательно читает эту строку по одному символу и также по одному символу выводит на печать до тех пор, пока не встретит символ *.
Теперь напишем несколько более полезную программу. Мы заставим ее подсчитывать символы, которые она читает. Нам требуется для этого ввести в предыдущую программу лишь некоторые изменения
/* подсчет символов! */
#define STOP *
main( )
{
char ch;
int count =0; /* инициализация счетчика символов 0 */
while ((ch = getchar( ))!= STOP)
{
putchar (ch);
count++; /* прибавить 1 к счетчику */
}
printf (" n Всего было прочитано %d символов n ' , count);
}
Чтение одной строки
Давайте подумаем, какие дополнительные усовершенствования можно ввести в программу, используя только те средства, которыми мы владеем. Первое, что легко можно сделать - это заменить признак окончания ввода данных. Но можно ли предложить что-то лучшее, чем символ *? Одной из возможностей является использование символа "нова строка" (n). Для этого нужно лишь переопределить признак STOP.
#define STOP ' n '
Какой это даст эффект? Очень большой ведь символ "новая стрoка" пересылается при нажатии клавиши [ввод], следовательно, в результате наша программа будет обрабатывать одну вводимую строку. Предположим, например, что мы внесли указанное изменение в программу подсчет символов1, а затем при ее выполнении ввели следующую строку:
О! Быть сейчас во Фресно, когда здесь лето, [ввод]
В ответ на экране появятся следующие строки
О! Быть сейчас во Фресно, когда здесь лeтo, Всего бьпо прсчитано 43 симвoлa
(Если бы мы не включили в управляющую строку оператора printf( ) в качестве первого символа признак n, второе сообщение появилось бы справа от запятой, после слова лето. Мы предпочли избежать такого склеивания строк).
Признак, появляющийся в результате нажатия клавиши [ввод] не входит в число символов (43), подсчитанных программой, поскольку подсчет осуществляется внутри цикла.
Чтение одиночного файла
Каким может быть идеальный признак STOP? Это должен быть такой символ, который обычно не используется в тексте и следовательно, не приводит к ситуации, когда он случайно встретится при вводе, и работа программы будет остановлена раньше чем мы хотели бы.
Проблема подобного сорта не нова, и, к счастью для нас, она уже была успешно решена проектировщиками вычислительных систем. На самом деле задача, которую они рассматривали, была не сколько отличной от нашей, но мы вполне можем воспользоваться их решением. Занимавшая их проблема касалась "файлов". Файлом можно назвать участок памяти, в который помещена некоторая информация. Обычно файл хранится в некоторой долговременной памяти, например на гибких или жестких дисках или на магнитной ленте. Чтобы отмечать, где кончается один файл и начинается другой, полезно иметь специальный символ, указывающий на конец файла. Это должен быть символ, который не может появиться где-нибудь в середине файла, точно так же как выше нам требовался символ, обычно не встречающийся во вводимом тексте. Решением указанной проблемы служит введение специального признака, называемого "End-of-File" (конец файла), или EOF, для краткости. Выбор конкретного признака EOF зависит от типа системы он может состоять даже из нескольких символов. Но такой признак всегда существует, и компилятор с языка Си, которым вы пользуетесь, конечно же "знает", как такой признак действует в вашей системе.
РИС. 6.4. Структура текстового файла с признаком EOF
Каким образом можно воспользоваться символом EOF? Обычно его определение содержится в файле . Общеупотребительным является определение
#define EOF (-1)
Это дает возможность использовать в программах выражения, подобные, например, такому
while ((ch=getchar( ))!= EOF)
Поэтому мы можем переписать нашу предыдущую программу, осущecтвляющyю ввод и эхо-печать символов, так:
/* ввод-вывод4 */
#include < stdio.h>
main( )
{
int ch;
while ((ch = getchar( ))! = EOF)
putchar (ch);
}
Отметим следующие моменты:
1. Нам не нужно самим определять признак EOF, поскольку заботу об этом берет на себя файл stdio.h.
2. Мы можем не интересоваться фактическим значением символа EOF, поскольку директива #define, имеющаяся в файле stdio.h, позволяет нам использовать его символическое представление.
3. Мы изменили тип переменной ch с char на int. Мы поступили так потому, что значениями переменных типа char являются целые числа без знака в диапазоне от 0 до 255, a признак EOF может иметь числовое значение -1. Эта величина недопустима для переменной типа char, но вполне подходит для переменной типа int. К счастью, функция getchar() фактически возвращает значение типа int, поэтому она в состоянии прочесть символ EOF.
4. Переменная ch целого типа никак не может повлиять на работу функции putchar( ). Она просто выводит на печать символьный эквивалент значения аргумента.
5. При работе с данной программой, когда символы вводятся с клавиатуры, необходимо уметь вводить признак EOF. He думайте, что вы можете просто указать буквы E-О-F или число -1. (Число -1 служит эквивалентом кода ASCII данного символа, а не самим этим символом. Вместо этого вам необходимо узнать, какое представление используется в вашей системе. В большинстве реализаций операционной системы UNIX, например, ввод знака [CTRL/d] (нажать на клавишу [d], держа нажатой клавишу [CTRL]) интерпретируется как признак EOF. Во многих микрокомпьютерах для той же цели используется знак [CTRL/z].
Приведем результат работы программы ввод-вывод4 в системе, обладающей буферизованным вводом:
0на идет во всей красе -
Она идет во всей красе -
Светла, как ночь ее страны.Светла, как ночь ее страны.
Лорд Байрон
Лорд Байрон
[CTRL/z]
Каждый раз при нажатии клавиши [ввод] производится обработка символов, попавших в буфер, и копия строки выводится на печать. Это продолжается до тех пор, пока мы не введем признак EOF.
Давайте остановимся и подумаем о возможностях программы ввод-вывод4. Она осуществляет вывод на экран символов независимо от того, откуда они поступают. Предположим, мы сумели сделать так, что программа вводит символы из некоторого файла. В этом случае она будет осуществлять вывод содержимого файла на экран и остановится только тогда, когда достигнет конца файла, поскольку обнаружит признак EOF. Или предположим, что у нас есть способ организовать вывод результатов работы программы в некоторый файл. Тогда можно набрать какой-нибудь текст на клавиатуре и при помощи программы ввод-вывод4 поместить его во внешнюю память. Или мы могли бы выполнить оба действия одновременно: например, осуществить ввод данных из одного файла и переслать их в другой. В этом случае программа ввод-вывод4 использовалась бы для копирования файлов. Следовательно, наша маленькая программа могла бы просматривать содержимое файлов, создавать новые файлы и получать копии файлов. Неплохо для такой короткой программы! Ключ к решению этих проблем - в управлении вводом и выводом. Это послужит темой представленного ниже обсуждения.