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

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

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

ся его адрес. В противном случае pLF получает значение NULL. Выражение *pLF позволяет получить доступ к соответствующему элементу массива name[], куда записывается признак конца строки '\0'.

Функция openPrograms() завершает выполнение программы, если файл с заданным именем предложен для повторной обработки либо не удалось открыть файл PROGRAMS для дополнения.

Следующей из основной программы вызывается функция

/* openKeyWords.c – функция чтения статистики предыдущих обработок */

#include <stdio.h> #include <string.h> #include "stWord.c"

FILE * openKeyWords(void)

{

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

resultFile = fopen(KEYWORDS, "r"); if (resultFile != NULL)

{for (i=0; i<SIZE_ARRAY; i++)

{fscanf(resultFile, "%s %d",

stWord[i].word, &stWord[i].counter);

}

fclose(resultFile);

}

resultFile = fopen(KEYWORDS, "w"); if (resultFile == NULL)

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

exit(0);

}

return resultFile;

}

Функция openKeyWords() должна "напомнить" программе результаты предыдущих обработок. Для этого в ней открывается для чтения (в режиме "r") файл KEYWORDS с результатами предыдущих анализов текста программ. Если этот файл уже существует (обрабатывается текст не первой программы), то данные из него считываются в массив структур struct keyword stWord[], определенный

531

в текстовом заголовочном файле stWord.c. Для этого в цикле с параметром i выполняется чтение из файла, представленного указателем FILE * resultFile. Для чтения используется библиотечная функция

fscanf(resultFile, "%s %d", stWord[I].word, &stWord[I].counter);

Указатель resultFile представляет файл KEYWORD, открытый только для чтения. Из файла в элементы структур stWord[i].word, stWord[i].counter последовательно считываются все сведения о предыдущих анализах текстов. После завершения цикла чтения файл, представленный указателем resultFile, закрывается и вновь открывается в режиме записи ("w"). Если файл KEYWORDS не существовал, то он сразу же открывается в режиме записи. Теперь в обоих случаях файл KEYWORDS подготовлен для обновления, которое будет выполнено после окончания обработки текста новой программы.

Функция openKeyWord() либо возвращает указатель на подготовленный для записи файл статистики KEYWORDS, либо завершает выполнение программы, если этот файл не удается открыть для записи.

Следующая функция, вызываемая в основной программе:

void programTest(char * progName);

Функция должна выполнить анализ текста из файла, имя которого передается ей в качестве параметра. Результаты анализа – статистика использования служебных слов – должны быть занесены в глобальный массив структур stWord[]. Первое действие функции – попытка открыть для чтения (в режиме "r") файл с именем, находящимся в строке, адресованной параметром progName. После открытия файла (адресованного теперь указателем FILE * inFile), из него в цикле выполняется чтение строк. Из очередной строки выбираются служебные слова и увеличиваются соответствующие счетчики в массиве структур stWord[]. По достижении конца файла он закрывается. Результаты выполнения функции programTest() сохраняются во внешнем (глобальном) массиве stWord[]. Текст функции:

532

/* programTest.c – функция подсчета использования служебных слов */

#include <stdio.h> #include <string.h> #include "stWord.c" #define SIZE_LINE 256

void programTest(char * progName)

{

FILE * inFile; int i, len;

char line[SIZE_LINE], temp[30]; char * string = line;

char * letters = "abcdefghijklmnopqrstuvwxyz"; inFile = fopen(progName, "r");

if (inFile == NULL)

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

exit (0);

}

while(fgets(line, SIZE_LINE, inFile) != NULL)

{string = line; while(1)

{string = strpbrk(string, letters); if (string == NULL) break;

len = strspn(string, letters); strncpy(temp, string, len); temp[len] ='\0';

string += len;

for (i=0; i < SIZE_ARRAY; i++)

if (strcmp(stWord[i].word, temp) == 0)

{stWord[i].counter++;

break;

}

}

}

fclose(inFile);

return;

}

Алгоритм функции после открытия файла соответствует такому псевдокоду:

533

Цикл до конца файла с текстом программы Прочитать строку из файла Цикл до конца строки

Выделить цепочку букв Цикл перебора служебных слов

Если цепочка есть служебное слово Увеличить счетчик слова Выйти из цикла

Конец-цикла (перебора слов) Конец-цикла (конец строки)

Конец-цикла (конец файла)

Закрыть файл с текстом программы

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

Функция programTest() либо завершает исполнение программы (когда не удалось открыть файл с предложенным именем программы), либо выполняет анализ текста из файла и изменяет массив stWord[].

Последняя функция finish(), вызываемая из main(), завершает обработку. Ее вызову предшествует уже описанная последовательность событий:

!открыт для изменений файл "статистики" KEYWORDS (адресуется указателем resultFile);

!открыт для дополнения файл PROGRAMS с именами ранее обработанных программ (адресуется указателем progFileName);

!известно и допустимо имя уже обработанного файла с текстом программы (progName);

!результаты анализа слов обработанного файла внесены в глобальный массив структур stWord[].

Действия функции:

Записать в файл KEYWORDS массив со статистикой Закрыть файл KEYWORDS

Записать имя программы в файл PROGRAMS Закрыть файл PROGRAMS

Вывести результаты обработки (итоговые)

Текст функции, завершающей выполнение программы:

534

/* finish.c – функция сохранения статистики служебных слов */

#include <stdio.h> #include "stWord.c"

void finish(FILE * resultFile,

FILE * progNameFile, char * progName)

{

int i;

for (i=0; i < SIZE_ARRAY; i++) fprintf(resultFile,"\n%s %d",

stWord[i].word, stWord[i].counter); fclose(resultFile);

fprintf(progNameFile, "\n%s", progName); fclose(progNameFile); printf("\nStatistics:\n");

for (i=0; i < SIZE_ARRAY; i++) if(stWord[i].counter != 0)

printf("\n%s - %d", stWord[i].word, stWord[i].counter);

return;

}

После записи в файл KEYWORDS функция выводит в стандартный выходной поток аккумулированные по всем предшествующим выполнениям результаты анализа. Печатаются служебные слова, которые встретились в текстах. (Для них счетчики отличны от нуля.) Для запуска программы 12_06.с, исполнимый модуль которой находится, например, в файле test.exe, используем директиву:

>test 12_02.c

Предполагается, что файл 12_02.с находится в том же каталоге,

что и text.exe.

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

Statistics: char - 2 for - 1

if - 2 int - 2

return – 2

535

Приведены результаты обработки первого текста. В файлах "programs" и "keyWords" накапливаются и сохраняются результаты всех исполнений программы. При повторном запуске программы с тем же аргументом

>test 12_02.c

получим сообщение:

Program "12_02.c" has been operated!

ЗАДАНИЕ. Измените приведенную программу 12_06.с таким образом, чтобы перейти от препроцессорной "сборки" текста к компоновке автономно оттранслированных функций.

В рассмотренной выше программе 12_06.с текст, поступающий на компиляцию, содержит все необходимые определения. Если по отдельности оттранслировать в объектные модули все функции программы и потом попытаться собрать их в исполнимый модуль, то ничего не получится. Во-первых, в основную программу следует включить прототипы вызываемых функций:

FILE * openPrograms(char * progName); FILE * openKeyWords(void);

void programTest(char * progName); void finish(FILE * resultFile,

FILE * progNameFile, char * progName);

Во-вторых, определение и инициализация массива структур stWord[] должны быть выполнены только один раз. При препроцессорной сборке текста программы 12_06.c защита от дублирования определений и описаний была обеспечена средствами условной компиляции (#ifndef…). При автономной трансляции модулей препроцессор не может помочь. Поэтому текст из файла stWord.c (т.е. определение структурного типа и массива структур stWord[]) нужно включить только в модуль с функцией main(). Во все остальные модули следует включать только такое описание массива структур (и, конечно, определение структурного типа):

536

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

struct keyWord{ char word[30]; int counter;

};

extern struct keyWord stWord[]; #define SIZE_ARRAY 32

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

Обратите внимание, что в описании массива структур необходимо использовать служебное слово extern (внешний) и нет указаний о размере массива.

Изменим в соответствии со сказанным тексты функций нашей программы и сохраним их в файлах:

m0.c – функция main(), включает текст из stWord.c; m1.c – функция openPrograms(), включает stArray.c; m2.c – функция openKeyWords(), включает stArray.c; m3.c – функция proramTest(), включает stArray.c; m4.c – функция finish(), включает stArray.c.

Теперь последовательно выполним компиляцию (без компоновки):

>gcc –c m0.c >gcc –c m1.c >gcc –c m2.c >gcc –c m3.c >gcc –c m4.c

Получим пять объектных модулей m0.o, m1.o, m2.o, m3.o, m4.o и выполним их сборку (компоновку) в исполнимый модуль программы:

>gcc –o new.exe m0.o m1.o m2.o m3.o m4.o

Получив файл new.exe, запустим его на исполнение:

>new.exe 12_04.c

537

Результаты выполнения программы (с учетом предыдущего):

Statistics:

char - 4 for - 1 if - 4 int - 5

return – 5 while - 1

Полученные числовые значения соответствуют не содержимому файла 12_04.с, а отображают анализ уже нескольких текстов, к которым приплюсованы результаты по программе 12_04.с. Названия соответствующих файлов сохранились в файле PROGRAMS.

12.3.Прямой доступ к данным файла

ЗАДАЧА 12-07. Проанализируйте текстовый файл и выведите на экран его строки в порядке возрастания их длин. Исходный файл оставьте без изменений.

В программе 11_15.с уже решена задача упорядочения символьных строк по их длинам. Но в том случае все строки находились в основной памяти. В задаче этого параграфа предполагается, что размер файла не позволяет все его строки одновременно разместить в основной памяти. Нужно "доставать" и печатать строки файла в заранее неизвестном порядке. Для этого сформируем список позиций начал строк анализируемого файла. Этот список легко упорядочить по длинам строк. Теперь, перебирая элементы списка и позиционируя файл с помощью значений позиций из списка, можно прочитать и вывести все строки файла в требуемом порядке.

Для простоты ограничим длину строк из файла. Будем последовательно прочитывать строки анализируемого файла в один и тот же достаточно большой символьный массив фиксированной длины (MAXLEN). MAXLEN введем как препроцессорную константу.

Кроме того, будем предполагать, что формируемый список позиций помещается в основной памяти, т.е. анализируемый текстовый файл не слишком велик.

538

Звенья списка будем определять как структуры типа

struct cell {/*звено списка*/

int lenStr; /*длина строки*/

long position; /*начало строки в файле*/ struct cell * next; /*следующее звено/

};

Обратите внимание, что позиция в файле всегда задается как значение типа long (элемент position в приведенном структурном типе).

Если позиция начала некоторой строки файла (long pos) и ее длина (int len) определены, то включить эти данные в список (адресуемый указателем struct cell * list) может следующая функция:

/* addCell.c – функция включения нового звена в список */

struct cell * addCell(struct cell * list, long pos, int len)

{

struct cell * new;

struct cell * current = list; struct cell * previous = NULL;

/* Выделить память для очередного звена списка: */ new=(struct cell *)malloc(sizeof(struct cell)); if (new == NULL)

{puts("\nERROR! No memory!"); exit(1);

}

new -> next = NULL; new -> lenStr = len; new -> position = pos;

/* Поиск в списке места для звена. */ while(1)

{ if (current == NULL)

{ if (previous == NULL)

return new; /* Списка не было. */ /* Дошли до конца списка: */ previous -> next = new;

return list;

}

539

if (new -> lenStr > current -> lenStr) { previous = current;

current = current -> next;

}

else

{new -> next = current;

if (previous == NULL) return new;

previous -> next = new; return list;

}

}

}

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

Когда список структур сформирован, с его помощью можно в произвольном порядке (заданном списком) прочитать строки из файла и вывести их на печать (в стандартный выходной поток). Сделаем это с помощью следующей функции:

/* printFile.c – функция упорядоченной печати строк файла */

void printFile(struct cell * list, FILE * testFile)

{

char line[MAXLEN]; struct cell * p = list; char * pLF = NULL; while(p != NULL)

{fseek(testFile, p -> position, SEEK_SET); fgets(line, MAXLEN, testFile); pLF=strchr(line,'\n');

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

puts(line);

p = p -> next;

}

}

540