1. 11. Лабораторная работа 11
Скачать 169.5 Kb.
|
Практикум по программированию на языке Си 1.11.Лабораторная работа 11Работа с файлами 1.11.1.Постановка задачиИмеется текстовый файл, содержащий произвольное количество строк. Длина каждой строки не превосходит 255 символов. Необходимо выполнить заданную обработку файла. 1.11.2.Варианты заданийВарианты заданий приведены в табл. 1.11. 1 Таблица 1.11. 1
Продолжение табл. 1.11. 1
Окончание табл.1.11. 1
1.11.3.Методические указания по выполнению лабораторной работыПриведем ряд рекомендаций, которые могут быть полезными при выполнении настоящей работы. Требуемую обработку следует оформить в виде функции пользователя. Входной и выходной файлы в эту функцию целесообразно передавать с помощью аппарата формальных и фактических параметров, а подготовительные файловые операции предпочтительнее выполнять в клиентском коде этой функции. К таким операциям относятся следующие операции: открытие и закрытие файла. В ряде задач в файле хранится числовая матрица. Это задачи 5 – 7, 9, 15, 17, 18, 22, 25 и 26. Первая строка файла в этих задачах содержит два числа, которые определяют количество строк и столбцов матрицы. Обработка файла может быть построена следующим образом. Вначале из файла читаются с помощью функции fscanf() два первых числа, которые записываются в две целочисленные переменные n и m. Затем следует организовать вложенные арифметические циклы. В задачах 8, 10 - 12 необходимо выполнить сортировку строк файла. Для выполнения сортировки можно воспользоваться любым способом. Например, можно использовать простейший способ – метод пузырьковой сортировки. В качестве примера ниже приводится функция для сортировки строк в порядке их возрастания (bibble). #define MAXLENGTH 128 void bibble(char arr[][MAXLENGTH + 1], int n) { int i, j; char buf[MAXLENGTH + 1]; for(i = 1; i < n; i++) { for(j = n - 1; j >=i; --j ) { if(strcmp(arr[j - 1], arr[j]) < 0) { strcpy(buf, arr[j - 1]); strcpy(arr[j - 1], arr[j]); strcpy(arr[j], buf); } } } } В ряде задач в файле находится произвольное количество строк, каждая из которых содержит произвольное количество чисел. К таким задачам относятся задачи 1 – 3, 13, 14, 16, 19 – 21. В этом случае обработка может состоять из вложенных итерационных циклов. Во внешнем цикле можно читать очередную строку, а во внутреннем цикле выполнять обработку строки с помощью функции strtod(). В процессе решения поставленной задачи следует продумать, какие структуры данных могут потребоваться для ее решения. В первую очередь, это относится к тем задачам, в которых в файле хранится матрица. В этих задачах необходимо выяснить требуется или нет при ее решении использовать двумерный массив. В ряде случаев необходимости в их применении нет. К числу таких задач относится, например задача 25. В этой задаче необходимо вычислить корень квадратный из суммы элементов строки числовой матрицы, хранящейся в текстовом файле. Здесь структура программы – вложенные арифметические циклы. Внутренний цикл должен выполнять суммирование квадратов элементов очередной строки, а внешний цикл – вычисляет корень квадратный из накопленной внутренним циклом суммы. Особенностью этой задачи является то положение, что прочитанное из файла число может сразу же быть обработано (просуммировано с квадратом) и необходимости в его хранении для последующей обработки нет. Совсем иначе дело обстоит при решении задачи 26. Здесь необходимо решить практически ту же задачу, что и в варианте 25, но применительно к столбцам матрицы. Здесь придется вначале прочитать весь файл в матрицу (двумерный числовой массив) и только затем приступить к его обработке. 1.11.4.Справочные материалыПри работе с внешними устройствами при программировании на языке Си следует использовать библиотечные функции. Библиотечные функции языка Си, предназначенные для работы с внешними устройствами можно разделить на две категории: низкоуровневые функции, потоковые функции. Низкоуровневые библиотечные функции учитывают особенности той среды, в которой выполняется программа. Это позволяет повысить их эффективность по сравнению с потоковыми функциями. Недостатком таких функций является ухудшение переносимости программ. Такие функции не поддерживаются стандартом языка Си. Потоковые функции нашли более широкое применение по сравнению с низкоуровневыми функциями. Этому способствовало то обстоятельство, что эти функции в стандарт языка Си. Они включены в стандартную библиотеку, которая является составной частью стандарта языка Си. В литературе рекомендуется использовать преимущественно те функции, которые входят в стандартную библиотеку языка Си. Ограничимся рассмотрением только потоковых библиотечных функций. Приведем описание стандартных подпрограмм, которые следует использовать при работе с файлами. Остановимся на некоторых понятиях, существенных для работы с потоковыми библиотечными функциями. Понятие потокаПоток – это своего рода обобщенное устройство ввода – вывода. Это понятие введено с целью обеспечить независимость программирования операций ввода – вывода от следующих факторов: конкретного типа внешнего устройства, среды, в которой выполняется программа. Все потоки ведут себя похожим образом. Их работа не учитывает особенности физического устройства. В этой связи функция, выполняющая чтение из дискового файла, может использоваться и для ввода с консоли. Потоки бывают двух категорий: текстовые, двоичные. Текстовый потокТекстовый поток – это последовательность символов, организованная в строки. Каждая строка должна заканчиваться символом новой строки: ‘\n’. Однако в конце последней строки этот символ не является обязательным. В зависимости от среды, в которой происходит выполнение программы, при использовании текстового потока может иметь место преобразование символов. Например, при записи в среде DOS или Windows символ ‘\n’ преобразуется в последовательность символов CRLF. Таким образом, при использовании текстового потока может и не иметь место однозначное соответствие между символами, которые записываются (читаются) и теми символами, которые хранятся на внешнем устройстве. Кроме того, при использовании текстового потока для чтения особым образом обрабатывается символ конца файла. В таблице кодов ASCII этот символ имеет код 0x1A. Двоичный потокДвоичный поток – это последовательность байтов, которая взаимно однозначно соответствует байтам на внешнем устройстве, причем никаких преобразований символов не происходит; отсутствуют символы, которые играют особую роль. Все символы обрабатываются одинаково. Количество символов, которые записываются (читаются) совпадает с количеством символов, хранящихся на внешнем устройстве. 1.11.4.1.Файлы в языке СИВ языке Си файлом называется любое внешнее устройство, начиная от дискового файла и кончая терминалами и принтерами. Поток связывается с определенным файлом при выполнении операции открытия. Как только файл будет открыт, можно выполнять обмен данными между ним и программой. Файл отключается от определенного потока при выполнении операции закрытия. При закрытии файла, открытого для записи, содержимое (если оно имеется) связанного с ним потока записывается на внешнее устройство. Этот процесс, называемый дозаписью потока гарантирует, что все данные будут записаны на внешнее устройство. 1.11.4.2.Указатель файлаУ каждого файла, связанного с потоком, имеется специальная управляющая структура, содержащая сведения о файле. Эта структура имеет тип FILE. Этот тип объявлен в заголовочном файле stdio.h. Для каждого файла, с которым должна работать программа должен существовать свой указатель файла. Указатель файла – это указатель на структуру типа FILE. Указатель файла представляет файл во всех операциях ввода – вывода.. 1.11.4.3.Функция fopenОбъявление этой функции имеет следующий вид. #include FILE* fopen(const char* filename, const char* mode); Эта функция открывает поток и связывает с ним определенный файл. Первый параметр (filename) рассматриваемой функции определяет открываемый файл. Второй параметр функции (mode) задает режим, в котором должен быть открыт файл. В случае успеха функция вернет указатель типа FILE* на созданную структуру данных. Этот указатель может использоваться файловыми функциями для выполнения операций ввода – вывода. В случае возникновения ошибки функция fopen() вернет нулевой указатель. В следующем фрагменте программного кода функция используется для открытия файла с именем file1.txt в режиме записи. Предполагается, что файл находится в текущем каталоге (иначе необходимо указывать путь к каталогу). Во фрагменте используется обычный прием, который состоит в проверке значения, возвращаемого функцией fopen(). FILE* fp; if((fp = fopen(“file1.txt”, “w”)) == NULL) { printf(“Не могу открыть файл file1.txt”); exit(1); } Возможные режимы открытия потоков представлены в таблице 1.11. 2. Таблица 1.11. 2.
1.11.4.4.Функция fcloseОбъявление этой функции имеет следующий вид. #include int fсlose(FILE* stream); Функция fclose() закрывает поток stream. Перед закрытием потока все связанные с ним буферы сбрасываются. При успешном выполнении функция fclose() возвращает 0, а в случае ошибки – EOF. 1.11.4.5.Функция feofОбъявление рассматриваемой функции имеет следующий вид: #include int feof(FILE* stream); Функция feof() проверяет, достигнут ли конец файла при выполнении очередной операции чтения. Параметр stream является указателем на поток, с которым работает файл. Если указатель текущей позиции файла установлен на конец файла, то функция feof() вернет ненулевое значение, в противном случае возвращается нуль. 1.11.4.6. Стандартные потокиВ начале выполнения программы автоматически открываются три стандартных потока (см. таблицу 1.12. 3) Таблица 1.11. 3.
1.11.4.7.Классификация функций потокового ввода – выводаВсе функции потокового ввода – вывода можно разделить на четыре категории: форматированный ввод – вывод, посимвольный ввод – вывод, построковый ввод – вывод, блоковый ввод – вывод. 1.11.4.8.Форматированный файловый ввод – выводПри форматированном вводе - выводе используются следующие функции: fprintf() – для записи в файл, fsсanf() - для чтения из файла. Функция fprintf() объявлена следующим образом: #include int fpintf(FILE* stream, const char* format[, аргумент, …] ); Рассматриваемая функция является обобщенным вариантом функции printf(). Здесь появился дополнительный параметр stream, которого нет в функции printf. Параметр stream является указателем на поток, который должен быть предварительно открыт функцией fopen(). Функция fprintf() возвращает число выведенных символов или отрицательное число, если произошла ошибка вывода. Функция fscanf() объявлена следующим образом: #include int fscanf(FILE* stream, const char* format[, указатель, …] ); Эта функция в свою очередь является обобщенным вариантом функции scanf(). Здесь также появился дополнительный параметр stream – указатель на поток. В результате своего выполнения функция fscanf() возвращает количество просмотренных, преобразованных и сохраненных входных полей; отсканированные, но несохраненные поля не учитываются. При достижении конца файла функция fscanf() возвращает значение EOF. Если ни одного поля не сохранено, то функция вернет нуль. 1.11.4.9.Построчный ввод – выводДля организации построкового ввода – вывода могут использоваться следующие функции: fputs(), fgets(). Функция fputs() объявлена следующим образом: #include int fputs(const char* s, FILE* stream); Функция fputs() выводит строку, на которую установлен указатель s в поток, на который установлен указатель stream. Символ ‘\0’, завершающий выводимую строку в поток не записывается. Если в процессе записи имела место ошибка, то рассматриваемая функция вернет EOF, в случае успеха возвращаемым значением функции fputs() является неотрицательное числа. Функция fgets() объявлена следующим образом: #include char* fputs(char* s, int n, FILE* stream); Функция fgets() читает не более n – 1 символов из потока на, который установлен указатель stream, в символьный массив, на который установлен указатель s. Существуют два способа завершения процесса чтения символов: прочитан символ новая строка (‘\n’) , прочитан n – 1 символ. В первом случае прочитанный символ новая строка записывается в символьный массив, а затем строка завершается нуль – символом. Во втором случае символ новая строка оказывается непрочтенным и незаписанным в символьный массив. Однако и в этом случае в символьный массив будет записан нуль – символ. После успешного выполнения функция fgets() возвращает указатель на символьный массив, в который производилась запись. Если имело место достижение конца файла при условии, что ни один символ не был прочитан, возвращаемым значением функции fscanf() является NULL, а содержимое массива, на который установлен указатель s, оказывается неименным. 1.11.4.10.Блоковый ввод – выводБлоковый ввод – вывод обеспечивается функциями fread() и fwrite(). Объявление функции fwrite() имеет следующий вид: #include size_t fwrite(const void* ptr, size_t n, size_t nmemb, FILE* stream); Функция fwrite() помещает до nmemb элементов (блоков), каждый из которых размером size, из массива адресуемого указателем ptr, в поток, на который установлен указатель stream. Функция fwrite() возвращает число успешно записанных элементов (блоков). Это значение может оказаться меньше, чем nmemb, если имеет место ошибка. #include size_t fread(void* ptr, size_t n, size_t nmemb, FILE* stream); Функция fwrite() читает до nmemb элементов (блоков), каждый из которых размером size, в массив адресуемого указателем ptr, из потока, на который установлен указатель stream. Функция fread() возвращает число успешно прочитанных элементов (блоков). Это значение может оказаться меньше, чем nmemb, если имеет место ошибка или обнаружен конец файла. 1.11.5.Примеры решенных задач1.11.5.1.Пример 1. Табулирование функции с записью результатов на диск1.11.5.1.1.Постановка эадачиТабулировать функцию y = в n равноостоящих точках, начиная от значения x = xn вплоть до x = xk. Результаты табулирования записать в текстовый файл. 1.11.5.1.2.РешениеОдин из возможных вариантов решения рассматриваемой задачи имеет следующий вид. /* Файл table.c. */ #include #include #include void table_file(double xn, double xk, int n, FILE* out); int main(void) { int n; double x1, x2; FILE* out_file; clrscr(); printf(“Количество расчетных точек = ”); scanf(“%d”, &n); printf(“Начальное значение аргумента=”); scanf(“%lf”, &x1); printf(“Конечное значение аргумента=”); scanf(“%lf”, &x2); if((out_file = fopen(“out_file.txt”, “w”) == NULL)) { printf(“Не могу открыть файл out_file.txt\n”); exit(1); } table_file(x1, x2, n, out_file); fclose(out_file); printf(“Файл сформирован\n”); getch(); return 0; } void table_file(double xn, double xk, int n, FILE* out) { double x, dx; int i ; fprintf(out, “%5s%15s%15s\n”, “Номер”, “Аргумент”, “Функция”); dx = (xk - xn) /(n - 1); x = xn; for(i = 1; i <= n; i++) { fprintf(out, “%5d%15.2f%15.2f\n”, i, x, x * x); x += dx; } } 1.11.5.2.Пример 2. Запись содержимого двух массивов в текстовый файл1.11.5.2.1.Постановка задачиИмеются два массива, содержащие числа типа double. Записать содержимое этих массивов в текстовый файл. Вывод организовать в виде таблицы, снабженной заголовком. Таблица должна содержать три колонки. В первой колонке должен выводиться порядковый номер элемента, в двух других элементы массивов. В заголовке следует указать имена выводимых массивов. 1.11.5.2.2.РешениеОформим решение данной задачи в виде функции. Эта функция должна иметь шесть параметров. Первый из них должен служить для передачи в функцию файлового указателя на файл, в который будет выполняться вывод. Два параметра необходимы для передачи массивов, значения которых следует выводить в файл. Затем еще два параметра необходимы для передачи в функцию имен выводимых массивов. Наконец, последний параметр должен служить для передачи в функцию количества выводимых элементов массива. #include void save_arrays(FILE* out, double* arr1, double* arr2, char* name1, char* name2, int num) { int i; fprinf(out, "%6s%15s%15s\n", "НОМЕР", name1, name2); for(i = 0; i < n; i++) { fprinf(out, "%6d%15.2f%15.2f\n", i, arr1[i], arr2[i]); } } 1.11.5.3.Пример 3. Чтение из таблицы, хранящейся в файле, двух числовых массивов1.11.5.3.1.Постановка задачиВ текстовом файле имеется таблица, в которой хранится содержимое двух массивов. Таблица содержит три колонки и снабжена заголовком, который находится в первой строке файла. В первой колонке представлен порядковый номер элемента, в двух других - элементы массивов. 1.11.5.3.2.РешениеОформим решение данной задачи в виде функции. Эта функция должна иметь три параметра. Первый из них должен служить для передачи в функцию файлового указателя на файл, из которого будет выполняться чтение. Два других параметра необходимы для передачи массивов, в которые будет выполняться чтение данных. Количество прочитанных элементов массивов функция должна вернуть в качестве возвращаемого значения. Для чтения файла будет использоваться функция fgets из стандартной библиотеки языка C. Основу алгоритма функции должен составлять итерационный цикл чтения файла. До входа в цикл необходимо прочитать заголовок таблицы, а в его теле последовательно читать строки таблицы. Строка, полученная в результате чтения заголовка таблицы, в программе не используется. #include /* Чтение массивов */ int load_arrays(FILE* in, double* arr1, double* arr2) { int count = 0; char buf[255]; /* Чтение заголовка таблицы */ fgets(buf, sizeof(buf), in); while(fgets(buf, sizeof(buf), in) != NULL) { sscanf(buf, "%*d%lf%lf", arr1 + count, arr2 + count); count++; } return count; } 1.11.5.4.Пример 4. Вывод содержимого текстового файла на экран дисплея с проверкой на наличие ошибок.#include #include void display_file(FILE* in) { char buf[255]; while(fgets(buf, sizeof(buf), in) != NULL) { fgets(buf, sizeof(buf), in); } if(ferror(in)) { printf("Ошибка чтения файла\n"); exit(1); } } 1.11.5.5.Пример 5. Числовая матрица находится в текстовом файле. Первая строка файла содержит данные о размере матрицы. Вычислить сумму элементов в каждой строке матрицы.#include #include void summa_row_in_text_file(FILE* in, FILE* out) { int nrow; int ncol; int row; int col; double s, x; fscanf(in, "%d%d", &row, &col); for(row = 0; row < nrow; row++) { s = 0; for(col = 0; col < ncol; col++) { fscanf(in, "%lf", &x); s += x; } fprintf(out, "%10.3f\n", s); } } 1.11.5.6.Пример 6. Сумма чисел в текстовом файле1.11.5.6.1.Постановка задачиВ текстовом файле находятся вещественные числа. Количество строк в файле и количество чисел в строке произвольно. Вычислить сумму чисел, находящихся в файле. Решение задачи оформим в виде функции. 1.11.5.6.2.РешениеДля вычисления суммы необходимо организовать итерационный цикл. Для чтения чисел можно воспользоваться функцией fscanf(), а для проверки условия нахождения в цикле можно использовать возвращаемое этой функцией значение. Первый вариант реализации. Обработка ошибок отсутствует#include /* Вычисление суммы чисел в текстовом файле */ double summa_in_text_file(FILE* in) { double s = 0; float x; while(fscanf(in, "%lf", &x) != EOF) { s += x; } return s; } Второй вариант реализации. Предусмотрена обработка ошибок чтения.#include #include /* Вычисление суммы чисел в текстовом файле */ double summa_in_text_file(FILE* in) { double s = 0; double x; int result; while((result = fscanf(in, "%lf", &x)) != EOF) { if(result != 1) { printf("Ошибка при форматировании прочтенных данных\n"); exit(1); } s += x; } if(ferror(in)) { printf("Ошибка при чтении файла\n"); exit(1); } return s; } 1.11.5.7.Пример 7./* Произвольный доступ, двоичный ввод - вывод */ #include #include #include #define ARSIZE 1000 double readNumber(FILE* in, int index); int main(void) { double numbers[ARSIZE]; double value; const char* f1 = "numbers.dat"; int i; long pos; FILE* in_out; /* Создаем массив чисел */ for(i = 0; i < ARSIZE; i++) numbers[i] = i * i; if((in_out = fopen(f1, "wb")) == NULL) { fprintf(stderr, "Невозможно открыть %s для вывода.\n", f1); exit(1); } /* Записываем числа в файл */ fwrite(numbers, sizeof *numbers, sizeof numbers, in_out); fclose(in_out); if((in_out = fopen(f1, "rb")) == NULL) { fprintf(stderr, "Невозможно открыть %s для произвольного\ ввода.\n", f1); exit(1); } /* Считываем выделенные объекты из файла */ printf("Введите индекс в диапазоне 0 - %d.\n", ARSIZE - 1); scanf("%d", &i); printf("Значение равно %g.\n", readNumber(in_out, i)); /* while(i >= 0 && i < ARSIZE) { pos = (long)i * sizeof * numbers; fseek(in_out, pos, SEEK_SET); fread(&value, sizeof *numbers, 1, in_out); printf("Значение равно %g.\n", value); printf("Следующий индекс (для выхода укажите индекс, не входящий в диапазон) : \n"); scanf("%d", &i); } */ fclose(in_out); puts("Пока"); getch(); return 0; } double readNumber(FILE* in, int index) { double value; long pos; pos = index * sizeof(double); fseek(in, pos, SEEK_SET); fread(&value, sizeof(double), 1, in); return value; } 1.11.6. Контрольные вопросы и задачи для самостоятельной работыВ чем состоит назначение файлов? В чем состоит отличие файлов от обычных переменных, объявляемых в программе? Какие виды библиотечных функций имеются в языке Си? В чем заключается различие между текстовыми и двоичными потоками? В чем состоит различие между потоком и файлом в языке Си? Каким образом объявляется файловый указатель? Из каких этапов складывается работа с файлами? Может ли текстовый файл использоваться в режиме прямого доступа? В чем состоит назначение процедуры Assign? Может ли текстовый поток быть открыт в режиме ввода – вывода? В каждой строке записано произвольное количество слов. Сформировать новый файл, дописав в конец каждой строки исходного файла ее номер. В конец каждой нечетной строки записать текст четной строки. Результаты вычислений записать в новый файл. Удалить из исходного файла все строки, длина которых не превосходит заданной величины. Поменять местами строки с четными и нечетными номерами. Результаты записать в новый файл. Имеется текстовый файл, содержащий действительные числа. Количество чисел в строке может быть любым. Количество строк не превосходит 100. Для каждой строки вычислить произведение содержащихся в ней чисел, а затем выполнить сортировку файла в порядке убывания произведения. Результаты сортировки записать в новый файл. Имеется текстовый файл, содержащий действительные числа. Количество чисел в строке может быть любым. Количество строк не превосходит 100. Для каждой строки вычислить сумму положительных чисел, а затем выполнить сортировку файла в порядке возрастания этой суммы. Результаты сортировки записать в новый файл. Имеется текстовый файл, содержащий действительные числа. Количество чисел в строке может быть любым. Количество строк не превосходит 100. Для каждой строки вычислить сумму положительных чисел, а затем выполнить сортировку файла в порядке убывания этой суммы. Результаты сортировки записать в новый файл. Имеется текстовый файл, содержащий действительные числа. Количество чисел в строке может быть любым. Количество строк не превосходит 100. Для каждой строки вычислить произведение положительных чисел, а затем выполнить сортировку файла в порядке убывания этого произведения. Результаты сортировки записать в новый файл. Имеется текстовый файл, содержащий действительные числа. Количество чисел в строке может быть любым. Количество строк не превосходит 100. Для каждой строки вычислить значение медианы, которое затем записать в начало рассматриваемой строки. Результаты записать в новый файл. Имеется текстовый файл, содержащий действительные числа. Количество чисел в строке может быть любым. Количество строк не превосходит 100. Для каждой строки вычислить значение медианы, которое затем записать в конец рассматриваемой строки. Результаты записать в новый файл. Имеется текстовый файл, содержащий действительные числа. Количество чисел в строке может быть любым. Количество строк не превосходит 100. Для каждой строки вычислить значение медианы. Результаты записать в конец исходного файла. Имеется текстовый файл, содержащий действительные числа. Количество чисел в строке может быть любым. Количество строк не превосходит 100. Для каждой строки вычислить значение медианы. Результаты вычислений записать в новый файл. В файле хранится числовая матрица. В первой строке файла записаны два числа: количество строк и столбцов матрицы, а затем сама матрица. Количество столбцов матрицы не превосходит 10. Выполнить для каждой строки матрицы сортировку в порядке возрастания значений содержащихся в ней чисел. Результаты вычислений записать в новый файл. В файле хранится числовая матрица. В первой строке файла записаны два числа: количество строк и столбцов матрицы, а затем сама матрица. Количество столбцов матрицы не превосходит 10. Выполнить для каждой строки матрицы сортировку в порядке уменьшения значений содержащихся в ней чисел. Результаты вычислений записать в новый файл. |