Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

книги / Практикум по программированию на языке Си

..pdf
Скачиваний:
25
Добавлен:
12.11.2023
Размер:
3.53 Mб
Скачать

int 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

дующих переменных: _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

Остальная часть программы совпадает с текстом программы 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

из файла символов получилось значение 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