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

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

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

ходного файла, нужно использовать режим "a" (от английского append – добавление):

outFile =fopen(outName,"a");

Программа приведена в файле 12_04_2.с. Результаты ее исполнения.

Первое исполнение:

Print the input file name: printFSTR.c <ENTER>

Print the output file name: res<ENTER>

The size of file: 520

Второе исполнение:

Print the input file name: 12_04.c<ENTER>

Print the output file name: res<ENTER>

The size of file: 880

Если до первого исполнения программы файл res не существовал, то при первом исполнении файл был создан и принял текст функции printFSTR.c. При втором исполнении в него дописан текст из файла 12_04.с.

ЗАДАНИЕ. Используя программу 12_04.с, выполните копирование текстового файла на дискету.

Оттранслировав программу 12_04.с и получив исполнимый модуль, можно ее выполнить с такими исходными данными:

Print the input file name: 12_01.c<ENTER>

Print the output file name: a:\temp.c<ENTER>

The size of file: 849

521

Тем самым файл с текстом программы 12_01.с будет скопирован в новый файл temp.c, размещенный на диске A:.

ЗАДАЧА 12-05. Напишите программу регистрации и обработки наблюдений за ежедневной температурой воздуха. При запуске программа запрашивает дату "dd-mm-yy" и температуру воздуха в градусах Цельсия "сс". После ввода данных для одного дня информация сохраняется в файле, выдается справка о средней температуре за весь период наблюдения, и выполнение программы завершается.

Так как к программе нужно обращаться не чаще, чем раз в сутки, то сохранение предыдущих записей целесообразно только в файле. Файл должен каждый раз открываться в режиме добавления данных. После окончания записи в файл очередной "порции" данных будем закрывать файл и открывать его заново в режиме чтения. Далее, читая записи файла, вычислим среднюю температуру.

Для обмена с файлом используем библиотечные функции форматного ввода-вывода:

int fprintf(FILE *stream, char *format, аргументы);

int fscanf(FILE *stream, char *format, аргументы);

Для проверки достижения конца файла потребуется библиотечная функция, которая возвращает 0, если конец файла не достигнут.

int feof(FILE * stream);

Закрывает обработку файла библиотечная функция:

int fclose(FILE * stream);

Только после закрытия файла его можно открыть для работы в другом режиме.

В программе необходимо предусмотреть проверку правильности ввода исходных данных. Будем считать, что температура не может выходить за пределы диапазона от –100 до +80 градусов Цельсия. Проверка правильности ввода значения температуры при этом не составит труда.

522

Дату будем вводить в виде трех целых чисел (переменные dd, mm, yy), представляющих, соответственно, число, месяц и год. Пусть год будет представлен только двумя значащими цифрами в диапазоне от 1 до 99 (будем решать задачу только для нашего столетия). Месяцы будем нумеровать от 1 (январь) до 12 (декабрь). С оценкой допустимости числа возникают некоторые сложности. Нужно учесть разное количество дней в разных месяцах и особенности високосного года. С учетом сказанного для проверки даты определим такую вспомогательную функцию:

// dateReal.c – функция проверки правильности даты int dateReal(int dd, int mm, int yy)

{

int maxDay[]= {31,28,31,30,31,30,31,31,30,31,30,31};

int max_dd;

if (yy < 1 || yy > 99) return 0; if (mm < 1 || mm > 12) return 0; max_dd = mm != 2 ? maxDate[mm-1] :

yy%4 == 0 ? 29 : 28;

if (dd < 1 || dd > max_dd) return 0; return 1;

}

Функция возвращает нулевое значение, если недопустимо сочетание значений аргументов, соответствующих параметрам dd (число), mm (месяц), yy (год нынешнего столетия). Если аргументы представляют допустимую дату, то возвращается значение 1.

Текст программы, решающей задачу:

/* 12_05.c - "наблюдения" за температурой */ #include <stdio.h>

#include "dateReal.c" int main ()

{

int i, sum; FILE * pFile;

char * fileName="temperature"; int dd, mm, yy, tm;

/* Открыть файл для дополнений: */ pFile = fopen(fileName, "a");

523

if (pFile == NULL)

{printf("\nThe file opening error!\n"); return 0;

}

/* Ввод исходных данных с проверкой допустимости: */ do

{printf("\nInput date (dd mm yy): "); scanf("%d%d%d", &dd, &mm, &yy);

}

while(dateReal(dd, mm, yy) == 0); do

{printf("\nInput temperature: "); scanf("%d", &tm);

}

while (tm < -100 || tm > 80); /* Открыть файл для чтения: */

fprintf(pFile,"\n%d %d %d %d", dd, mm, yy, tm); fclose(pFile);

pFile = fopen(fileName, "r"); if (pFile == NULL)

{ printf("\nThe file opening error!\n"); return 0;

}

for (i=0, sum=0; !feof(pFile); i++)

{fscanf(pFile,"%d%d%d%d", &dd, &mm, &yy, &tm); sum+=tm;

}

printf("\nThe average temperature: %g", (double)sum/i);

return 0;

}

Результаты первого выполнения программы:

Input date (dd mm yy): 1<ENTER> 14<ENTER>

02<ENTER>

Input date (dd mm yy): 1 2 3<ENTER> Input temperature: -14<ENTER>

The average temperature: -14

524

Результаты второго выполнения программы:

Input date (dd mm yy): 2 2 3<ENTER>

Input temperature: -10<ENTER>

The average temperature: -12

В программе используется фиксированное имя файла "temperature". Он создается при первом исполнении программы и дополняется одной строкой при каждом следующем исполнении. При открытии файла с ним связывается "поток" pFile. Затем выполняется ввод данных с проверкой их корректности. Функция fprint() добавляет в файл строку с введенными данными. Функция fclose() закрывает файл, т.е. разрывает связь потока pFile с именем файла "temperature". Файл сразу же открывается в режиме чтения и функция fscanf() в цикле до конца файла считывает из него данные. Значения температуры суммируются в переменной sum. Параметр цикла (переменная i) служит счетчиком записей. Остальное очевидно. В результатах приведен диалог двух сеансов работы с программой. Здесь обратите внимание на среднюю температуру.

12.2.Обработка файлов в потоковом режиме

ЗАДАЧА 12-06. Напишите программу сбора статистики использования служебных слов в текстах программ на языке Си. При каждом обращении к программе в качестве аргумента в командной строке должно быть указано имя анализируемого файла с текстом на Си. Каждый файл должен обрабатываться только один раз.

В данной задаче требуется от одного сеанса до следующего сохранять сведения об уже обработанных файлах с текстами на языке Си и информацию о частотах использования служебных слов в проанализированных текстах. Для сохранения указанных сведений используем два текстовых файла: файл "keyWords" – для сбора статистики, файл "programs" – для имен уже просмотренных файлов с программами на языке Си.

525

Оба файла будем обрабатывать в режиме последовательного доступа. Введем соглашения о форме представления информации в каждом из этих файлов. Файл "programs" будет содержать последовательность строк, в каждой из которых разместим имя одной уже просмотренной программы:

prog1.c

prog2.c

Файл "keyWords" будет содержать последовательность строк ви-

да: "служебное_слово счетчик". Например:

auto 0 break 2

Несмотря на отсутствие алгоритмических сложностей в поставленной задаче, размер текста программы достаточно велик, чтобы проектировать ее в "лоб". Используем классической подход разработки "сверху-вниз", т.е. вначале напишем основную функцию программы. В этой функции main() предусмотрим вызов функций, решающих частные задачи обработки.

И в функции main(), и в вызываемых ею функциях потребуется список (набор) служебных слов языка Си и удобная схема сопоставления каждого слова с количеством его появлений в анализируемом тексте. Используем массив структур, похожий на тот, который мы определили в программах 11_06.c и 11_11.с. Точнее, введем структурный тип struct keyword с двумя элементами: массив фиксированного размера для служебного слова и счетчик (int counter) появлений этого слова в анализируемых текстах. Определим и инициализируем массив struct keyword stWord[] из элементов этого типа.

Пусть и структурный тип, и массив структур будут внешними по отношению к функциям программы. Выделим их определения в отдельный текстовый файл такого вида:

/* stWord.c – определение массива структур для служебных слов */

#ifndef STRUCT_KEY_WORD

526

#define STRUCT_KEY_WORD

 

 

struct keyWord{ char word[30];

 

int

counter;

 

 

};

 

 

 

#define SIZE_ARRAY 32

 

 

struct keyWord

stWord[SIZE_ARRAY] =

"char",0,

{"auto",0,

"break",0,

"case",0,

"const",0,

"continue",0,

"default",0,

"do",0,

"double",0,

"else", 0,

"enum",0,

"extern",0,

"float",0,

"for",0,

"goto",0,

"if",0,

"int",0,

"long",0,

"register",0,

"return",0,

"short",0,

"signed",0, "sizeof",0,

"static",0,

"struct",0,

"switch",0,

 

"typedef",0,

"union",0,

"unsigned",0,

"void",0,

"volatile",0,

"while",0 };

 

#define PROGRAMS "programs" #define KEYWORDS "keyWords" #endif

В приведенном тексте из файла stWord.c обратите внимание на препроцессорные директивы. Они, во-первых, определяют три препроцессорных константы: SIZE_ARRAY – количество элементов в массиве структур stWord[]; PROGRAMS и KEYWORD – имена файлов, применяемых в программе. Роль других препроцессорных директив – защита от непреднамеренного дублирования определений при неоднократном включении файла в текст программы. Для этого используются препроцессорные директивы #ifndef, #endif и введен препроцессорный идентификатор STRUCT_KEY_WORD. (Этот механизм условной компиляции мы уже подробно рассматривали в темах, посвященных препроцессорной обработке.)

Восновной программе определим: указатель FILE * resultFile на файл (keyWords) со статистикой; указатель FILE * progNameFile на файл (programs) с именами уже обработанных программ.

Всоответствии с условием при каждом запуске программы в командной строке необходимо указывать имя очередного файла с обрабатываемым текстом. Получить доступ к имени можно с помощью второго аргумента функции main().

Псевдокод решения задачи может быть таким:

1. Проверить наличие имени анализируемого файла в аргументах функции main()

527

Если имени нет, – завершить выполнение

2. Проверить по файлу PROGRAMS необходимость анализа файла с текстом на Си.

Если файл уже обрабатывался, – завершить выполнение. Иначе – подготовиться к обработке текста (открыть файл с текстом на Си).

3.Прочитать из файла статистику – результаты обработок предыдущих текстов.

4.Выполнить анализ текста, обновляя статистику

5.Сохранить статистику в файлах, напечатать результаты.

Пункт 1 псевдокода тривиален, а для реализации пунктов 2 – 5 нужны вспомогательные, более детализированные алгоритмы. Запланируем их реализацию в виде отдельных функций и представим основную программу таким образом:

/* 12_06.c - сбор статистики использования служебных

#include

слов */

<stdio.h>

#include

<string.h>

#include

"stWord.c"

#include

"openPrograms.c"

#include

"openKeyWords.c"

#include

"programTest.c"

#include

"finish.c"

int main

(int nArg, char * arg[])

{

 

FILE * resultFile; FILE * progNameFile; if (nArg == 1)

{printf("\nThe file name is absent!"); return 0;

}

progNameFile = openPrograms(arg[1]); resultFile = openKeyWords(); programTest(arg[1]);

finish(resultFile, progNameFile, arg[1]); return 0;

}

В программе 12_06.с (после проверки наличия аргумента в командной строке) первой вызывается еще не известная нам функция с прототипом:

528

FILE * openPrograms(char * progName);

Ей в качестве аргумента передается имя, использованное в командной строке при запуске программы. Задача функции – проверить файл PROGRAMS с именами уже обработанных программ, разыскивая в файле имя (progName) программы, предлагаемой для обработки. Здесь возможны следующие варианты. Файл еще не создан, т.е. обрабатывается первая программа. В этом случае нужно создать файл (открыть его заново для записи или дополнения) и вернуть указатель на него.

Если файл PROGRAMS существует, то в нем выполняется поиск имени программы (параметр progName). Для этого в цикле до конца файла выполняется проверка его строк. Если очередная строка совпадет с именем программы, то обработка завершена – текст программы с этим именем уже обработан. В противном случае файл закрывается и открывается для дополнений (в режиме "a"). Текст функции:

/* openPrograms.c – функция проверки необходимости анализа */

#include <stdio.h> #include <string.h> #include "stWord.c" #define NAME_SIZE 40

FILE * openPrograms(char * progName)

{

int number, i, count=1; FILE * progFile;

char name[NAME_SIZE], *pLF=NULL; progFile = fopen(PROGRAMS, "r"); if (progFile == NULL)

/* Файл не существует - обрабатывается первая программа. */

{progFile = fopen(PROGRAMS, "w"); if (progFile == NULL)

{printf("\nThe file \"%s\" opening error!\n",

PROGRAMS);

exit(1);

}

return progFile;

529

}

/* Файл PROGRAMS уже есть - ищем в нем имя программы. */

while (!feof(progFile))

{fgets(name, NAME_SIZE, progFile); pLF=strchr(name,'\n');

if (pLF != NULL) *pLF='\0';

if (strcmp(name, progName) == 0)

{/* Программа была уже обработана. */ printf

("\nProgram \"%s\" has been operated!\n", progName);

exit(1);

}

}

/* Файл PROGRAMS есть, программа не обработана. */ fclose(progFile);

progFile = fopen(PROGRAMS, "a"); if (progFile == NULL)

{ printf("\nThe file \"%s\" opening error!\n", PROGRAMS);

exit(1);

}

return progFile;

}

В тексте функции openPrograms() обратите внимание на указатель char *pLF. Роль его вспомогательная, но достаточно важная. В теле функции строка, адресованная параметром char *progName, должна последовательно (в цикле до конца файла) сравниваться со строками, которые прочитываются в массив char name[] из файла PROGRAMS. Чтение выполняет библиотечная функция fgets(), которая прочитывает символ перевода строки '\n' и помещает его перед сим- волом-ограничителем '\0'. Это происходит для всех строк файла, кроме последней. В строке, представляемой параметром progName, отсутствует символ '\n', но всегда есть терминальный символ '\0'. Тем самым возникают затруднения при сравнении строк библиотечной функцией strcmp(). Чтобы их устранить, используется библиотечная функция поиска в строке заданного символа strchr(). В строке, прочитанной из файла функцией fgets() в массив name, ищется позиция символа '\n'. Если такой символ есть, то указателю pLF присваивает-

530