
Информатика первый курс / ЛИТЕРАТУРА / СИ / Функции ввода-вывода
.docОчень часто делают ошибку в функции fputc, путая порядок ее аргументов. Запомните навсегда - указатель файла идет вторым!
int fputc( int c, FILE *fp );
Известная вам функция printf также является частью библиотеки stdio. Она входит в семейство функций:
FILE *fp;
char bf[256];
fprintf(fp, fmt, ... );
printf( fmt, ... );
sprintf(bf, fmt, ... );
Первая из функций форматирует свои аргументы в соответствии с форматом, заданным строкой fmt (она содержит форматы в виде %-ов) и записывает строку-результат посимвольно (вызывая putc) в файл fp. Вторая - это всего-навсего fprintf с каналом fp равным stdout. Третяя выдает сформатированную строку не в файл, а записывает ее в массив bf. В конце строки sprintf добавляет нулевой байт '\0' - признак конца.
Для чтения данных по формату используются функции семейства
fscanf(fp, fmt, /* адреса арг-тов */...);
scanf( fmt, ... );
sscanf(bf, fmt, ... );
Функции fprintf и fscanf являются наиболее мощным средством работы с текстовыми файлами (содержащими изображение данных в виде печатных символов).
4.14.
Текстовые файлы (имеющие строчную организацию) хранятся на диске как линейные массивы байт. Для разделения строк в них используется символ '\n'. Так, например, текст
стр1
стрк2
кнц
хранится как массив
с т р 1 \n с т р к 2 \n к н ц длина=14 байт
!
указатель чтения/записи (read/write pointer RWptr)
(расстояние в байтах от начала файла)
При выводе на экран дисплея символ \n преобразуется драйвером терминалов в последовательность \r\n, которая возвращает курсор в начало строки ('\r') и опускает курсор на строку вниз ('\n'), то есть курсор переходит в начало следующей строки.
В MS DOS строки в файле на диске разделяются двумя символами \r\n и при выводе на экран никаких преобразований не делается*****. Зато библиотечные функции языка Си преобразуют эту последовательность при чтении из файла в \n, а при записи в файл превращают \n в \r\n, поскольку в Си считается, что строки разделяются только \n. Для работы с файлом без таких преобразований, его надо открывать как "бинарный":
FILE *fp = fopen( имя, "rb" ); /* b - binary */
int fd = open ( имя, O_RDONLY | O_BINARY );
'\n' - '\012' (10) line feed
'\r' - '\015' (13) carriage return
'\t' - '\011' (9) tab
'\b' - '\010' (8) backspace
'\f' - '\014' (12) form feed
'\a' - '\007' (7) audio bell (alert)
'\0' - 0. null byte
Все нетекстовые файлы в MS DOS надо открывать именно так, иначе могут произойти разные неприятности. Например, если мы программой копируем нетекстовый файл в текстовом режиме, то одиночный символ \n будет считан в программу как \n, но записан в новый файл как пара \r\n. Поэтому новый файл будет отличаться от оригинала (что для файлов с данными и программ совершенно недопустимо!).
Задание: напишите программу подсчета строк и символов в файле. Указание: надо подсчитать число символов '\n' в файле и учесть, что последняя строка файла может не иметь этого символа на конце. Поэтому если последний символ файла (тот, который вы прочитаете самым последним) не есть '\n', то добавьте к счетчику строк 1.
4.15.
Напишите программу подсчета количества вхождений каждого из символов алфавита в файл и печатающую результат в виде таблицы в 4 колонки. (Указание: заведите массив из 256 счетчиков. Для больших файлов счетчики должны быть типа long).
4.16.
Почему вводимый при помощи функций getchar() и getc(fp) символ должен описываться типом int а не char?
Ответ: функция getchar() сообщает о конце файла тем, что возвращает значение EOF (end of file), равное целому числу (-1). Это НЕ символ кодировки ASCII, поскольку getchar() может прочесть из файла любой символ кодировки (кодировка содержит символы с кодами 0...255), а специальный признак, который не должен совпадать ни с одним из хранимых в файле символов. Поэтому для его хранения требуется больше одного байта (нужен хотя бы еще 1 бит). Проверка на конец файла в программе обычно выглядит так:
...
while((ch = getchar()) != EOF ){
putchar(ch);
...
}
-
ПОЯСНЕНИЯ
-
Пусть ch имеет тип unsigned char. Тогда ch всегда лежит в интервале 0...255 и НИКОГДА не будет равно (-1). Даже если getchar() вернет такое значение, оно будет приведено к типу unsigned char обрубанием и станет равным 255. При сравнении с целым (-1) оно расширится в int добавлением нулей слева и станет равно 255. Таким образом, наша программа никогда не завершится, т.к. вместо признака конца файла она будет читать символ с кодом 255 (255 != -1).
-
Пусть ch имеет тип signed char. Тогда перед сравнением с целым числом EOF байт ch будет приведен к типу signed int при помощи расширения знакового бита (7ого). Если getchar вернет значение (-1), то оно будет сначала в присваивании значения байту ch обрублено до типа char: 255; но в сравнении с EOF значение 255 будет приведено к типу int и получится (-1). Таким образом, истинный конец файла будет обнаружен. Но теперь, если из файла будет прочитан настоящий символ с кодом 255, он будет приведен в сравнении к целому значению (-1) и будет также воспринят как конец файла. Таким образом, если в нашем файле окажется символ с кодом 255, то программа воспримет его как фальшивый конец файла и оставит весь остаток файла необработанным (а в нетекстовых файлах такие символы - не редкость).
-
Пусть ch имеет тип int или unsigned int (больше 8 бит). Тогда все корректно.
В MS DOS в текстовых файлах признак конца (EOF) хранится явно и обозначается символом CTRL/Z. Поэтому, если программным путем записать куда-нибудь в середину файла символ CTRL/Z, то некоторые программы перестанут "видеть" остаток файла после этого символа!
Наконец отметим, что разные функции при достижении конца файла выдают разные значения:
scanf, fscanf, fgetc, getc, getchar выдают EOF,
read - выдает 0,
gets, fgets - NULL.
4.17.
Напишите программу, которая запрашивает ваше имя и приветствует вас. Для ввода имени используйте стандартные библиотечные функции
gets(s);
fgets(s,slen,fp);
В чем разница?
Ответ: функция gets() читает строку (завершающуюся '\n') из канала fp==stdin. Она не контролирует длину буфера, в которую считывается строка, поэтому если строка окажется слишком длинной - ваша программа повредит свою память (и аварийно завершится). Единственный возможный совет - делайте буфер достаточно большим (очень туманное понятие!), чтобы вместить максимально возможную (длинную) строку.
Функция fgets() контролирует длину строки: если строка на входе окажется длиннее, чем slen символов, то остаток строки не будет прочитан в буфер s, а будет оставлен "на потом". Следующий вызов fgets прочитает этот сохраненный остаток. Кроме того, fgets, в отличие от gets, не обрубает символ '\n' на конце строки, что доставляет нам дополнительные хлопоты по его уничтожению, поскольку в Си "нормальные" строки завершаются просто '\0', а не "\n\0".
FILE *fp = ... ;
char buffer[512];
int len;
...
while(fgets(buffer, sizeof buffer, fp))
{
if((len = strlen(buffer)) && buffer[len-1] == '\n')
buffer[--len] = '\0'; // @
printf("%s\n", buffer);
}
Здесь len - длина строки. Если бы мы выбросили оператор, помеченный '@', то printf печатал бы текст через строку, поскольку выдавал бы код '\n' дважды - из строки buffer и из формата "%s\n".
Если в файле больше нет строк (файл дочитан до конца), то функции gets и fgets возвращают значение NULL. Обратите внимание, что NULL, а не EOF. Пока файл не дочитан, эти функции возвращают свой первый аргумент - адрес буфера, в который была записана очередная строка файла.
Фрагмент для обрубания символа перевода строки может выглядеть еще так:
#include <stdio.h>
#include <string.h>
char buffer[512];
FILE *fp = ... ;
char *sptr;
while(fgets(buffer, sizeof(buffer), fp) != NULL)
{
if(sptr = strchr(buffer, '\n')) *sptr = '\0';
printf("%s\n", buffer);
}
4.18.
В чем отличие puts(s); и fputs(s,fp); ?
Ответ: puts выдает строку s в канал stdout. При этом puts выдает сначала строку s, а затем - дополнительно - символ перевода строки '\n'. Функция же fputs символ перевода строки не добавляет. Упрощенно:
fputs(s, fp) char *s; FILE *fp;
{ while(*s) putc(*s++, fp); }
puts(s) char *s;
{ fputs(s, stdout); putchar('\n'); }