![](/user_photo/_userpic.png)
книги / Практикум по программированию на языке Си
..pdfint main()
{
puts("\nThe stdin data:"); printFSTR(stdin); puts("\nThe stdout data:"); printFSTR(stdout); puts("\nThe stderr data:"); printFSTR(stderr);
}
Результаты выполнения программы:
The stdin data: fstr -> _cnt=0
fstr -> _ptr=(null) fstr -> _base=(null) fstr -> _bufsiz=0 fstr -> _flag=12 fstr -> _file=0
fstr -> _name_to_remove=(null)
The stdout data: fstr -> _cnt=0
fstr -> _ptr=fstr -> _ptr=fstr -> _ptr=fstr - fstr -> _base=fstr -> _base=fstr -> _base=fstr -> _base=fstr
fstr -> _bufsiz=16384 fstr -> _flag=53
fstr -> _file=1
fstr -> _name_to_remove=(null)
The stderr data: fstr -> _cnt=0
fstr -> _ptr=(null) fstr -> _base=(null) fstr -> _bufsiz=0 fstr -> _flag=18 fstr -> _file=2
fstr -> _name_to_remove=(null)
В результатах выполнения программы обратите внимание на значения для разных файлов (для разных стандартных потоков) сле-
511
![](/html/65386/197/html_6uGuvU_xKP.t62Q/htmlconvd-WWj79B512x1.jpg)
дующих переменных: _bufsiz – размер внутреннего буфера; _flag – флаги состояния файла; _file – префикс или дескриптор файла. Подробнее об операциях с файлами, которыми пользуется операционная система, можно прочитать в руководствах по соответствующим ОС. Для MS-DOS можно воспользоваться [15].
ЗАДАЧА 12-02. Напишите программу, которая читает строки своего исходного текста (из файла) и выводит их "порциями", не превышающими стандартную высоту экрана. Рассмотрите вариант, когда имя файла с текстом программы известно и явно задано в виде строковой константы в программе.
Зная имя файла, нужно открыть этот файл для последовательного чтения. Затем в цикле можно читать и выводить "порции" текста до достижения конца файла. Так как максимальная длина строк не задана, то возможны два подхода – использование заведомо большого символьного массива и применение динамического массива "настраиваемого" на длину строки, читаемой из файла. В первом случае чтение удобно выполнять функцией fgets(). Во втором случае чтение строки в растущий массив должно выполняться посимвольно, например функцией fgetc().
Используем функцию fgets(), т.е. будем считывать строки из файла в "большой" символьный массив фиксированной длины. Прототип библиотечной функции:
char * fgets(char * string, int n, FILE *stream);
Здесь string – указатель на символьный массив (в который выполняется чтение), stream – указатель, представляющий входной поток (файл). Функция при достижении конца файла, представленного указателем stream, возвращает значение NULL. В противном случае из этого файла в массив, адресованный указателем string, считывается не более чем (n-1) очередных символов. За ними в массив автоматически заносится признак конца строки '\0'.
Чтобы указатель FILE * stream представлял конкретный файл, необходимо открыть файл, т.е. "связать" указатель stream с именем файла. Это выполнит библиотечная функция с прототипом:
FILE * fopen(char *filename, char * mode);
512
Здесь filename – указатель на строку, содержащую имя файла, mode – указатель на строку, определяющую режим работы с файлом. Нам требуется "чтение в текстовом режиме", т.е. строка должна иметь вид "r".
Обработку будем выполнять в цикле до достижения конца файла с текстом программы.
/* 12_02.c - вывод текста программы "порциями" */ #include <stdio.h>
#define SIZE_STR 80 #define DOSE 5
int main ()
{
int count=1; FILE * pFile;
char * fileName = "12_02.c"; char line[SIZE_STR];
pFile = fopen(fileName, "r"); if (pFile == NULL)
{ printf("\nThe file \"%s\" is absent!", fileName);
return 0;
}
printf("\nText from the file \"%s\":\n\n", fileName);
for (count=2; fgets(line, SIZE_STR, pFile)!=NULL; count++)
{ if (count%DOSE == 0)
getchar(); /* ожидание "сигнала" от пользователя */
printf("%s",line);
}
return 0;
}
В программе препроцессорные константы определяют предельную длину читаемой строки (SIZE_STR) и количество строк в порции вывода на экран (DOSE). Текущий номер строки фиксирует счетчик count. Введены указатель на поток (pFile) и указатель на строку с именем файла (fileName). После выполнения функции
513
fopen() выполняется проверка успешности. Если pFile==NULL, то считается, что файл с исходным текстом программы отсутствует в том каталоге, где размещен исполнимый модуль программы, и исполнение программы завершается. В противном случае начинается цикл обработки файла. Как только прочитаны и напечатаны первые (DOSE-2) строки, по условию (count%DOSE==0) вызывается библиотечная функция getchar() и программа останавливается, ожидая сигнала от клавиатуры. По нажатию ENTER цикл обработки продолжается. В следующих (за первой) итерациях выводятся DOSE строк. Последняя "порция" может быть меньше, так как цикл завершается по достижении конца файла, когда fgets() вернет значение
NULL.
ЗАДАНИЕ. Модифицируйте программу 12_02.с, чтобы она вывела на экран текст файла из другого каталога, а не тот, в котором находится исполнимый модуль программы.
Изменения коснутся только одной строки:
char * fileName="12_02.c";
В этом определении указателя fileName нужно заменить сокращенное имя программы 12_12.с полным именем того файла, из которого нужно выполнять чтение текста. Здесь есть тонкость, иногда приводящая к ошибкам. Предположим, что мы хотим вывести текст программы 11_02.с, находящейся в каталоге c:\practicum\programs\11\.
Определение указателя fileName должно быть таким:
char * fileName="c:\\practicum\\programs\\11\\11_02.с";
О необходимости изображать символ '\' в строке в виде "\\" начинающие часто забывают. Остальное очевидно (см. программу
12_02_1.с).
ЗАДАЧА 12-03. На основе решения предыдущей задачи напишите программу, которая выводит "порциями" на экран строки текстового файла, имя которого задано в командной строке при
514
запуске программы. Если в командной строке отсутствует имя файла (нет аргумента), то программа должна печатать свой собственный текст. Ограничение – исполнимый модуль программы и файл с ее исходным текстом должны находиться в одном каталоге и иметь имена, отличающиеся только расширением
(".exe" и ".c").
Предлагаемая задача является развитием предыдущей и никаких новых механизмов работы с файлами не требует. По существу, в этой задаче нужно продемонстрировать знание средств работы со строками Си (тема 9) и вспомнить, как определяются параметры функции main().
Напомним, что у функции main() первый параметр (типа int) – это количество элементов массива с элементами типа char *, а второй параметр – адрес этого массива. Каждый элемент этого массива адресует символьную строку. Первый из них (элемент массива с нулевым индексом) адресует строку с полным адресом исполняемой программы. Второй указывает на строку с аргументом, явно заданным в командной строке при запуске программы на исполнение.
/* 12_03.c - вывод файла или текста программы */ #include <stdio.h>
#include <string.h> #define SIZE_STR 80 #define DOSE 5
int main (int nArg, char * arg[])
{
int count=1; FILE * pFile; char * fileName;
char line[SIZE_STR]; char * ind = NULL; if (nArg == 1)
{fileName = arg[0];
ind = strrchr(fileName, '.');
*(ind+1) = 'c'; *(ind+2) = '\0';
}
else
fileName = arg[1];
pFile = fopen(fileName, "r");
515
if (pFile == NULL)
{ printf("\nThe file \"%s\" is absent!", fileName);
return 0;
}
printf("\nText from the file \"%s\":\n\n", fileName);
for (count=2; fgets(line, SIZE_STR, pFile)!=NULL; count++)
{ if (count%DOSE == 0)
getchar();/* ожидание "сигнала" от пользователя */
printf("%s",line);
}
return 0;
}
Программа представляет собой усовершенствованный вариант программы 12_02.с. Если первый аргумент функции main() отличен от 1, то указателю fileName присваивается значение второго элемента массива char * arg[]. В противном случае fileName получает значение arg[0] и выполняется преобразование строки, которую этот указатель адресует.
Поясним, как это выполняется. Предположим, что при компиляции использована директива:
c:\programs\12>gcc –o 12_03.exe 12_03.c
При запуске программы на исполнение из каталога c:\programs\12 используется имя ее exe-файла 12_03.exe.
В этом случае arg[0] (и fileName) адресуют строку:
c:\programs\12>12_03.exe
Библиотечная функция strrchr() определяет в строке, адресованной указателем fileName, "последнее" (самое правое) вхождение символа '.'. Найденный адрес (значение переменной char * ind) используется для формирования из строки с именем exe-модуля имени файла с исходным текстом программы. В нашем примере будет сформирована строка с таким содержанием:
c:\programs\12\12_03.с
516
![](/html/65386/197/html_6uGuvU_xKP.t62Q/htmlconvd-WWj79B517x1.jpg)
Остальная часть программы совпадает с текстом программы 12_02.с. Как и там, указатель fileName адресует строку с названием текстового файла.
ЗАДАЧА 12-04. Напишите программу копирования текстового файла в новый файл. Имена файлов (исходного и копии) вводите с клавиатуры. При копировании подсчитывайте размер файла (в символах) и печатайте подсчитанное значение.
Обработку информации в этой задаче удобно выполнять посимвольно, т.е. использовать для чтения функцию:
int fgetc(FILE * stream);
Функция возвращает в виде значения типа int очередной символ, прочитанный из файла, связанного с указателем stream.
Для записи одного символа в файл будем использовать библиотечную функцию
int fputs(int symbol,FILE * stream);
Функция помещает символ symbol (представленный кодом типа int) в очередную позицию файла, связанного с указателем stream. Возвращаемое значение – записанный символ либо значение EOF в случае ошибки при записи.
Файл для чтения мы уже открывали. Для создания нового файла, в который должно выполняться копирование, в функции fopen() используется режим обмена "w" (запись в файл).
Основные действия решения задачи:
Прочитать имена файлов Открыть файл для записи Открыть файл для чтения Цикл до конца читаемого файла
Прочитать символ из исходного файла Увеличить счетчик символов Записать символ в выходной файл
Конец-цикла Напечатать сведения о размере файла Закрыть файлы
517
Приведенному псевдокоду соответствует следующая программа:
// 12_04.c - копирование файла с вычислением объема
#include <stdio.h> #include <string.h> int main ()
{
int size=0; FILE * inFile; FILE * outFile;
char inName[60]; char outName[60]; int simbol;
puts("Print the input file name: "); gets(inName);
inFile = fopen(inName, "r"); if (inFile == NULL)
{printf("\nThe file \"%s\" is absent!", inName); return 0;
}
puts("Print the output file name: "); gets(outName);
outFile = fopen(outName, "w"); if (outFile == NULL)
{ printf("\nThe file opening error!\n"); return 0;
}
while ((simbol=fgetc(inFile)) != EOF)
{size ++;
fputc(simbol, outFile);
}
printf("\nThe size of file: %d", size); return 0;
}
Результаты выполнения программы:
Print the input file name: printFSTR.c<ENTER>
518
Print the output file name: res.c<ENTER>
The size of file: 520
В тексте программы определены: счетчик символов (size); указатель на исходный файл (inFile); указатель на файл с копией (outFile). Для имен файлов определены символьные массивы inName и outName. Для чтения каждого отдельного символа используется не символьная, а целочисленная переменная symbol. Это позволяет считывать значение EOF. После открытия исходного файла для чтения
inFile=fopen(inName,"r");
выполняется проверка значения указателя inFile. Если файл не найден (т.е. inFile==NULL) – выдается сообщение и работа прекращается.
После открытия файла для копии:
outFile=fopen(outName,"w");
условие (outFile==NULL) позволяет проверить возможность и успешность создания файла. Например, диск может быть закрыт для чтения или на нем нет места. В этом случае выдается сообщение, и работа завершается.
Если оба файла успешно открыты – выполняется цикл чтениязаписи с подсчетом введенных символов.
Интересно сравнить в приведенных результатах подсчитанное количество символов файла с тем объемом файла, который определяет операционная система. В нашем примере копировался файл printFSTR.c с текстом функции. Получен результат volume=520. Оценка объема файла printFSTR.c средствами операционной системы – 540 байт. (Можете проверить.) Однако это не ошибка, а особенность чтения файла в текстовом режиме. Каждая пара символов "возврат каретки" (CR с кодом 13) и "перевод строки" (LF с кодом 10) при чтении заменяются одним символом "новая строка" ('\n'). Так как в тексте функции printFSTR.c 20 строк (можете проверить прямым подсчетом), то именно поэтому при подсчете прочитанных
519
![](/html/65386/197/html_6uGuvU_xKP.t62Q/htmlconvd-WWj79B520x1.jpg)
из файла символов получилось значение 540–20=520. Стоит обратить внимание и на размер файла res с копией. В нашем примере он занимает 540 байт, так как при записи каждый символ конца строки '\n' заменен на пару символов CR и LF. Таким образом, копирование выполняется без искажений.
ЗАДАНИЕ. В программе копирования 12_04.с замените режим текстового чтения-записи на режим двоичного (бинарного) обмена.
Для этого придется изменить только два оператора с обращениями к функциям открытия файлов таким образом (см. программу
12_04_1.с):
inFile=fopen(inName,"rb");
outFile=fopen(outName,"wb");
Результаты исполнения программы:
Print the input file name: printFSTR.c <ENTER>
Print the output file name: res<ENTER>
The size of file: 540
Теперь количество обработанных программой символов совпало с размерами файлов в байтах.
Обратите внимание, что при многократных открытиях файла с одним именем в режиме "w" или "wb" не создаются новые экземпляры файла, а всегда сохраняется только последняя копия.
ЗАДАНИЕ. Напишите программу для "приписывания" текста из заданного файла к тексту другого уже существующего файла (конкатенация файлов).
Разбирать программу, реализующую эту задачу, не станем – это простейшая модификация программы 12_04.с. При открытии вы-
520