файле символов. Поэтому для его хранения требуется больше одного байта (нужен хотя
бы еще 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 бит). Тогда все корректно.
Отметим, что в UNIX признак конца файла в самом файле физически НЕ ХРАНИТСЯ. Система
в любой момент времени знает длину файла с точностью до одного байта; признак EOF
вырабатывается стандартными функциями тогда, когда обнаруживается, что указатель чте-
ния достиг конца файла (то есть позиция чтения стала равной длине файла - последний
байт уже прочитан).
В MS DOS же в текстовых файлах признак конца (EOF) хранится явно и обозначается
символом CTRL/Z. Поэтому, если программным путем записать куда-нибудь в середину
файла символ CTRL/Z, то некоторые программы перестанут "видеть" остаток файла после
этого символа!
Наконец отметим, что разные функции при достижении конца файла выдают разные
значения: scanf, fscanf, fgetc, getc, getchar выдают EOF, read - выдает 0, а gets,
fgets - NULL.
А. Богатырев, 1992-95 - 153 - Си в UNIX
4.17. Напишите программу, которая запрашивает ваше имя и приветствует вас. Для ввода
имени используйте стандартные библиотечные функции
gets(s);
fgets(s,slen,fp);
В чем разница?
Ответ: функция gets() читает строку (завершающуюся '\n') из канала fp==stdin.
Она не контролирует длину буфера, в которую считывается строка, поэтому если строка
окажется слишком длинной - ваша программа повредит свою память (и аварийно завер-
шится). Единственный возможный совет - делайте буфер достаточно большим (очень туман-
ное понятие!), чтобы вместить максимально возможную (длинную) строку.
Функция fgets() контролирует длину строки: если строка на входе окажется длин-
нее, чем slen символов, то остаток строки не будет прочитан в буфер s, а будет остав-
лен "на потом". Следующий вызов fgets прочитает этот сохраненный остаток. Кроме того
fgets, в отличие от gets, не обрубает символ '\n' на конце строки, что доставляет нам
дополнительные хлопоты по его уничтожению, поскольку в Си "нормальные" строки завер-
шаются просто '\0', а не "\n\0".
char buffer[512]; FILE *fp = ... ; 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
#include
char buffer[512]; FILE *fp = ... ;
...
while(fgets(buffer, sizeof buffer, fp) != NULL){
char *sptr;
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'); }
А. Богатырев, 1992-95 - 154 - Си в UNIX
4.19. Найдите ошибки в программе:
#include
main() {
int fp;
int i;
char str[20];
fp = fopen("файл");
fgets(stdin, str, sizeof str);
for( i = 0; i < 40; i++ );
fputs(fp, "Текст, выводимый в файл:%s",str );
fclose("файл");
}
Мораль: надо быть внимательнее к формату вызова и смыслу библиотечных функций.
4.20. Напишите программу, которая распечатывает самую длинную строку из файла ввода
и ее длину.
4.21. Напишите программу, которая выдает n-ую строку файла. Номер строки и имя
файла задаются как аргументы main().
4.22. Напишите программу
slice -сКакой +сколько файл
которая выдает сколько строк файла файл, начиная со строки номер сКакой (нумерация
строк с единицы).
#include
#include
long line, count, nline, ncount; /* нули */
char buf[512];
void main(int argc, char **argv){
char c; FILE *fp;
argc--; argv++;
/* Разбор ключей */
while((c = **argv) == '-' || c == '+'){
long atol(), val; char *s = &(*argv)[1];
if( isdigit(*s)){
val = atol(s);
if(c == '-') nline = val;
else ncount = val;
} else fprintf(stderr,"Неизвестный ключ %s\n", s-1);
argc--; ++argv;
}
if( !*argv ) fp = stdin;
else if((fp = fopen(*argv, "r")) == NULL){
fprintf(stderr, "Не могу читать %s\n", *argv);
exit(1);
}
for(line=1, count=0; fgets(buf, sizeof buf, fp); line++){
if(line >= nline){
fputs(buf, stdout); count++;
}
if(ncount && count == ncount)
break;
}
А. Богатырев, 1992-95 - 155 - Си в UNIX
fclose(fp); /* это не обязательно писать явно */
}
/* End_Of_File */
4.23. Составьте программу, которая распечатывает последние n строк файла ввода.
4.24. Напишите программу, которая делит входной файл на файлы по n строк в каждом.
4.25. Напишите программу, которая читает 2 файла и печатает их вперемежку: одна
строка из первого файла, другая - из второго. Придумайте, как поступить, если файлы
содержат разное число строк.
4.26. Напишите программу сравнения двух файлов, которая будет печатать первую из
различающихся строк и позицию символа, в котором они различаются.
4.27. Напишите программу для интерактивной работы с файлом. Сначала у вас запраши-
вается имя файла, а затем вам выдается меню:
1. Записать текст в файл.
2. Дописать текст к концу файла.
3. Просмотреть файл.
4. Удалить файл.
5. Закончить работу.
Текст вводится в файл построчно с клавиатуры. Конец ввода - EOF (т.е. CTRL/D), либо
одиночный символ '.' в начале строки. Выдавайте число введенных строк.
Просмотр файла должен вестись постранично: после выдачи очередной порции строк
выдавайте подсказку
--more-- _
(курсор остается в той же строке и обозначен подчерком) и ожидайте нажатия клавиши.
Ответ 'q' завершает просмотр. Если файл, который вы хотите просмотреть, не сущест-
вует - выдавайте сообщение об ошибке.
После выполнения действия программа вновь запрашивает имя файла. Если вы отве-
тите вводом пустой строки (сразу нажмете , то должно использоваться имя файла,
введенное на предыдущем шаге. Имя файла, предлагаемое по умолчанию, принято писать в
запросе в [] скобках.
Введите имя файла [oldfile.txt]: _
Когда вы научитесь работать с экраном дисплея (см. главу "Экранные библиотеки"),
перепишите меню и выдачу сообщений с использованием позиционирования курсора в задан-
ное место экрана и с выделением текста инверсией. Для выбора имени файла предложите
меню: отсортированный список имен всех файлов текущего каталога (по поводу получения
списка файлов см. главу про взаимодействие с UNIX). Просто для распечатки текущего
каталога на экране можно также использовать вызов
system("ls -x");
а для считывания каталога в программу[*]
FILE *fp = popen("ls *.c", "r");
... fgets(...,fp); ... // в цикле, пока не EOF
pclose(fp);
(в этом примере читаются только имена .c файлов).
4.28. Напишите программу удаления n-ой строки из файла; вставки строки после m-ой.
К сожалению, это возможно только путем переписывания всего файла в другое место (без
будет вполне законно, поскольку в данном случае
sp
- не имя массива (т.е. константа,
А. Богатырев, 1992-95 - 156 - Си в UNIX
4.29. Составьте программу перекодировки текста, набитого в кодировке КОИ-8, в аль-
тернативную кодировку и наоборот. Для этого следует составить таблицу перекодировки
из 256 символов: c_new=TABLE[c_old]; Для решения обратной задачи используйте стан-
дартную функцию strchr(). Программа читает один файл и создает новый.
4.30. Напишите программу, делящую большой файл на куски заданного размера (не в
строках, а в килобайтах). Эта программа может применяться для записи слишком боль-
шого файла на дискеты (файл режется на части и записывается на несколько дискет).
#include
#include
#define min(a,b) (((a) < (b)) ? (a) : (b))
#define KB 1024 /* килобайт */
#define PORTION (20L* KB) /* < 32768 */
long ONEFILESIZE = (300L* KB);
extern char *strrchr(char *, char);
extern long atol (char *);
extern errno; /* системный код ошибки */
char buf[PORTION]; /* буфер для копирования */
void main (int ac, char *av[]) {
char name[128], *s, *prog = av[0];
int cnt=0, done=0, fdin, fdout;
/* M_UNIX автоматически определяется
* компилятором в UNIX */
#ifndef M_UNIX /* т.е. MS DOS */
extern int _fmode; _fmode = O_BINARY;
/* Задает режим открытия и создания ВСЕХ файлов */
#endif
if(av[1] && *av[1] == '-'){ /* размер одного куска */
ONEFILESIZE = atol(av[1]+1) * KB; av++; ac--;
}
if (ac < 2){
fprintf(stderr, "Usage: %s [-size] file\n", prog);
exit(1);
}
if ((fdin = open (av[1], O_RDONLY)) < 0) {
fprintf (stderr, "Cannot read %s\n", av[1]); exit (2);
}
if ((s = strrchr (av[1], '.'))!= NULL) *s = '\0';