практику

advertisement
Практика №8. Ввод-вывод строк. Текстовые файлы
Необходимое вступление
Текстовые файлы являются простым, но широко используемым способом хранения
информации. Любая операционная система использует такие файлы для хранения своих
настроек, параметров запуска программ и т.п.
Обработка текстового файла может проводиться как посимвольно, так и построчно.
Первый способ удобен для решения сравнительно несложных задач, когда обработка
каждого символа ведется независимо от других. Например, таким способом можно легко
подсчитать количество знаков препинания в заданном файле.
Второй способ является более универсальным и распространенным, здесь из файла
в цикле последовательно считываются и обрабатываются все содержащиеся в нем строки
символов. Так удобно обрабатывать файлы, подготовленные в текстовом редакторе или
записанные программой, использующей разбиение результирующего файла на строки.
Для считывания строки из файла используется буфер - массив символов, длина
которого достаточна для обработки самой длиной строки файла. Как правило,. для
обработки всех строк буфер нужен только один, поэтому размер его можно выбирать с
некоторым запасом. Объявляется буфер следующим образом:
char Buf[256]; // В файле не должно быть строк длиной более 255 символов
Для работы с файлами используются структуры и стандартные функции,
определенные в использовавшемся Вами ранее заголовочном файле <stdio.h>. В
программе необходимо описать и использовать указатели на управляющие структуры для
каждого используемого файла:
FILE *fIn, *fOut; // Для входного и выходного файлов
Значение этим указателям присваивается специальной функцией открытия файлов,
например:
fIn = fopen("input.txt", "rt"); // Для входного файла
fOut = fopen("output.txt", "wt"); // Для выходного файла
Первый параметр этой функции определяет путь открываемому файлу (по
умолчанию считается, что файл находится в текущем каталоге, что и рекомендуется
использовать при отладке программы). Второй параметр задает операцию, для которой
открывается файл: r - чтение, w - запись, a - добавление строк. Символ t во втором
параметре определяет, что файл текстовый, а не двоичный.
Если путь указан неверно или файл не может быть открыть по какой-либо другой
причине (например, не хватает памяти для размещения управляющей структуры), то
функция открытия файла возвращает значение NULL. Это значение нужно обязательно
проверять, желательно сразу после попытки открыть файл.
Для ввода и вывода строк рекомендуется использовать стандартные функции
fgets() и fputs():
1
char *fgets(char *Buf, int num, FILE * fIn);
int fputs(const char *str, FILE *fOut);
Функция fgets() читает из входного файла fIn не более num-1 символов и помещает
их в массив символов, адресуемый указателем Buf. Символы читаются до тех пор, пока не
будет прочитан символ новой строки или значение EOF, либо пока не будет достигнут
заданный предел. По завершении чтения символов сразу же за последним из них
размещается нулевой символ. Символ новой строки сохраняется и становится частью
массива Buf.
Функция fputs() записывает в заданный файл fOut содержимое строки, адресуемой
указателем str. При этом завершающий нулевой символ (т.е. символ конца строки ('0')) не
записывается. Обе вышеприведенные функции нормально обрабатывают строки,
содержащие внутри себя пробелы.
После завершения работы с файлом его необходимо закрыть:
fclose(fIn);
// Для входного файла
fclose(fOut); // Для выходного файла
Пример решения простой задачи
Задача 1: Дана текстовый файл. Определить в нем количество строчных латинских и
русских букв.
Для решения данной задачи воспользуемся программой, приведенной в практике
«Строковые типы». Там решалась аналогичная задача для символьной строки, которая
вводилась с клавиатуры. Поскольку обработка символов производится по одному, то
используем функцию посимвольного чтения из текстового файла fgetc():
int fgetc( FILE * fIn);
Функция возвращает очередной считанный из файла символ в виде целого
числа. Если достигается конец файла или происходит ошибка чтения, функция возвращает
значение EOF.
Определение принадлежности любого символа заданной группе символов будем
также выполнять с помощью отдельной функции isLetter(). В данном примере эта
функция используется два раза для каждого символа: проверяется, является ли он
строчной английской буквой или строчной русской соответственно.
2
Текст программы:
#include<stdio.h>
#include<locale.h>
// Функция проверки: принадлежит ли указанный символ заданному алфавиту
bool isLetter(char c, char abc[])
{
for(int i=0; abc[i]!=0; i++) // Цикл по строке с алфавитом
{
if(abc[i] == c)
return true;
// Символ найден
}
return false;
// Символ не найден
}
int _tmain(int argc, _TCHAR* argv[])
{
// Задаем алфавиты для русских и английских букв
char smallEnglish[] = "qwertyuioplkjhgfdsazxcvbnm";
char smallRussian[] = "йцукенгшщзхъэждлорпавыфячсмитьбю";
char str[256];
int n_str_lat = 0;
int n_str_rus = 0;
int c;
// Буфер для вода строки
// Количество строчных латинских
// Количество строчных русских
// Считанный из файла символ
FILE *fIn = fopen("input.txt", "rt"); // Открытие исходного файла
if(fIn == NULL)
{
printf("Input.txt - File Not Found!\n");
return 1;
}
setlocale(LC_ALL, "Russian");
// Для ввода-вывода русских букв
c = fgetc(fIn);
// Считываем первый символ
do
// Цикл по всем символам в файле
{
if(isLetter(c, smallEnglish))
// Проверка латинских
n_str_lat++;
else
if(isLetter(c, smallRussian)) // Проверка русских
n_str_rus++;
c = fgetc(fIn);
}
while (c != EOF);
// Считываем очередной символ
// Пока файл не закончится
// Печать результата работы
printf("n_str_lat = %d, n_str_rus = %d \n", n_str_lat, n_str_rus);
return 0;
}
Пример решения задачи средней сложности
Задача 2: Дан текстовый файл, содержащий программу на языке Паскаль. Заменит
в этом файле все вхождения слов begin и end на открывающуюся и закрывающуюся
фигурные скобки. Вывести на консоль количество строк, в которых произведены замены.
Измененную программу записать в новый файл.
3
В данной задаче речь идет о замене одной последовательности символов в строке
исходного файла на другую. Таких последовательностей может быть несколько, поэтому
удобно сохранить исходные последовательности в одном массиве, а последовательности
для замены – в другом. Для этого можно использовать два строковых массива:
char *InStr[] = {"begin", "end", ""};
char *OutStr[] = {"{", "}", ""};
Первый массив задает строки для замены, второй – строки, на которые нужно
заменить найденные. Количество элементов в этих массивах должно совпадать.
Количество строк для замены можно хранить в отдельной переменной, но часто
используется добавление в конец массива строк еще одной пустой строки, по которой
функция, обрабатывающая этот массив, может завершить цикл обработки. В данном
примере использован именно этот подход, поэтому в каждый из массивов добавлена
последним элементом пустая строка.
Для открытия входного и выходного файлов используются отдельные функции,
которые при невозможности открыть файл завершают работу всей программы вызовом
функции exit(). Третья функция выполняет заданную замену и возвращает количество
измененных строк. При этом учитывается тот факт, что слово для замены в одной строке
файла может встречаться несколько раз.
Текст программы:
#include
#include
#include
#include
"stdafx.h"
<stdio.h>
<process.h>
<string.h>
FILE * OpenFile(char fname[]);
FILE * CreateFile(char fname[]);
int
TryReplace(FILE *in, FILE *out, char *InStr[], char *OutStr[]);
int _tmain(int argc, _TCHAR* argv[])
{
FILE *in, *out;
char *InStr[] = {"begin", "end", ""};
char *OutStr[] = {"{", "}", ""};
// Указатели на входной и выходной файл
// Строки, которые нужно заменить
// Строки, на которые нужно заменить
in = OpenFile("input.txt");
out = CreateFile("output.txt");
// Открытие входного файла
// Открытие выходного файла
int c = TryReplace(in, out, InStr, OutStr);
// Обработка файла
if(c == 0)
// Проверка результата
printf("No strings to replace\n");
else
printf("Found %d strings to replace\n", c);
fclose(in);
fclose(out);
return 0;
// Закрытие файлов
}
// Функция замены строк, заданных в массиве InStr[] на строки из массива OutStr[].
// Строки для замены считываются из файла in, измененные строки записываются в файл out
// Возвращается количество измененных строк
4
int
{
TryReplace(FILE *in, FILE *out, char *InStr[], char *OutStr[])
int count = 0;
char buf[1024];
// Счетчик измененных строк
// Буфер для чтения строки из файла
while(fgets(buf, 1023, in)) // Пока в файле есть строки
{
while(true) // Ищем в прочитанной строке строки для замены
{
int flag = 0;
// Цикл по всем строкам, которые нужно заменить
for(int i=0; InStr[i][0]!=0; i++)
{
// Определяем вхождение строки для замены
char *p = strstr(buf, InStr[i]);
if(p != NULL)
{
flag = 1;
// В строке были замены
*p = 0;
// Отделяем часть строки до заменяемой
fputs(buf, out);// Выводим часть строки до заменяемой
fputs(OutStr[i], out);
// Выводим заменяющую строку
strcpy(buf, p+strlen(InStr[i])); // Сдвигаем строку
count++;
// Считаем замены
break;
// Прерываем цикл, чтобы заново искать
}
}
if(flag == 0) // Прерываем цикл, замен не было
break;
}
fputs(buf, out);
// Выводим считанную строку без замен или
// остатки строки после последней замены
}
return count; // Возвращаем счетчик измененных строк
}
// Открытие исходного файла с проверкой
FILE * OpenFile(char fname[])
{
FILE *tmp = fopen(fname, "rt");
if(tmp == NULL)
{
printf("input file not found\n");
exit(1);
}
return tmp;
}
// Открытие результирующего файла с проверкой
FILE * CreateFile(char fname[])
{
FILE *tmp = fopen(fname, "wt");
if(tmp == NULL)
{
printf("output file not created\n");
exit(2);
}
return tmp;
}
5
Пример решения задачи повышенной сложности
Задача 3: В текстовом файле задан неформатированный текст программы на
Паскале. Записать этот текст в другой файл, добавив отступы в соответствии с
вложенностью блоков программы.
Текст программы:
#include <stdio.h>
#include <process.h>
#include <string.h>
// Описания функций программы
FILE
FILE
void
char
int
* OpenFile(char fname[]);
* CreateFile(char fname[]);
FormatFile(FILE *in, FILE *out);
* FindFirst(char buf[], char block_open[], char block_close[]);
OutputString(char buf[], char block[], FILE *out, int count);
char* indent = "
";
// Один отступ
int _tmain(int argc, _TCHAR* argv[])
{
FILE *in, *out;
in = OpenFile("input.txt");
out = CreateFile("output.txt");
// Входной и выходной файлы
// Открываем входной с проверкой
// Открываем выходной с проверкой
FormatFile(in, out);
// Форматируем входной файл
fclose(in);
fclose(out);
return 0;
// Закрываем все файлы
}
// Форматирование исходного файла на Паскале, запись в выходной файл
void
{
FormatFile(FILE *in, FILE *out)
int count = 0;
// Счетчик отступов
char* block_open = "begin"; // Начало блока
char* type_open = "type"; // Начало описания типа
char* all_close = "end";
// Конец блока или типа
char buf[1024];
while(fgets(buf, 1023, in))
{
if(*buf == 0) continue;
// Пока в исходном файле есть строки
// Пропускаем пустые строки
while(true) // Ищем в прочитанной строке строки BEGIN, TYPE или END
{
// Определение первого из трех заданных слов
char *first = FindFirst(buf, block_open, all_close);
first = FindFirst(buf, type_open, first);
// Ни одно слово не найдено, отступ не меняем
if(first == NULL && *buf != 0)
{
for(int i=0; i<count; i++)
// Выводим отступы
6
fprintf(out, "%s", indent);
fputs(buf, out); // Выводим считанную строку без замен или
// остатки строки (пустые строки не выводим)
break;
}
if (*first == 'b')
// Обрабатываем begin
{
count = OutputString(buf, "begin", out, count);
}
else if (*first == 't')
// Обрабатываем type
{
count = OutputString(buf, "type", out, count);
}
else if(*first == 'e')
// Обрабатываем end
{
count = OutputString(buf, "end", out, count-1);
}
if(*buf == 0 || *buf == '\n')
break;
// Пропускаем пустые строки
}
}
}
// Обработка строки с найденным началом или концом блока
int OutputString(char buf[], char block[], FILE *out, int count)
{
char *p = strstr(buf, block);
// Определяем вхождение слова в строку
for(int i=0; i<count; i++)
// Выводим отступы
fprintf(out, "%s", indent);
if(block == "begin" || block == "type")
{
if(*(p+strlen(block)+1) != '\n') // Если найденное слово не последнее
{
char *pspace = strchr(p, ' ');
// Находим позицию пробела
if(pspace) *pspace = 0; // Отделяем часть строки после найденной
}
fputs(buf, out);
strcpy(buf, p+strlen(block)+1);
// Выводим часть строки включая найденную
// Сдвигаем строку вперед
if(*buf != 0)
// После BEGIN, если он не последний в строке
fprintf(out, "\n"); // - новая строка
return count+1;
}
else
{
// Увеличиваем отступ
// block == "end"
if(p > buf) // Перед END что-то есть
{
*(p - 1) = 0;
// Отрезаем часть строки перед END
fprintf(out, "%s", indent); // Добавляем отступ
fprintf(out, "%s\n", buf); // Выводим часть строки
strcpy(buf, p);
// Сдвигаем строку вперед
return count+1;
// Увеличиваем отступ
}
else
{
7
fprintf(out, "end%c\n", *(p+3)); // Выводим END и символ после него
strcpy(buf, p+strlen(block)+1);
// Сдвигаем строку вперед
return count;
// Не меняем отступ
}
}
}
// Поиск в строке одной из двух заданных подстрок
// Если есть обе, возвращается указатель на первую из них
// Если только одна из двух - указатель на нее, Если ни одной - NULL
char * FindFirst(char buf[], char* block_open, char* block_close)
{
// Проверяем случай с нулевыми параметрами
if( block_open == NULL && block_close == NULL)
return NULL;
else if( block_open == NULL)
return strstr(buf, block_close);
else if( block_close == NULL)
return strstr(buf, block_open);
// Обе строки ненулевые, ищем первое вхождение
char *p1 = strstr(buf, block_open);
char *p2 = strstr(buf, block_close);
if(p1 == NULL && p2 == NULL)
return NULL;
else if(p1 == NULL)
return p2;
else if(p2 == NULL)
return p1;
else
{
if(p1 < p2)
return p1;
else
return p2;
}
// Обе строки отсутствуют
// Первая отсутствует, вторая есть
// Вторая отсутствует, первая есть
// Обе есть, определяем левую из них
}
// Открытие исходного файла для чтения
FILE * OpenFile(char fname[])
{
FILE *tmp = fopen(fname, "rt");
if(tmp == NULL)
{
printf("input file not found\n");
exit(1);
}
return tmp;
}
// Открытие выходного файла для записи
FILE * CreateFile(char fname[])
{
FILE *tmp = fopen(fname, "wt");
if(tmp == NULL)
{
printf("output file not created\n");
exit(2);
}
return tmp;
}
8
Варианты задач для выполнения
Для каждой из нижеследующих задач написать функцию main() и одну или несколько
функций, выполняющих заданные действия.
Простые задачи
1. Дан текстовый файл, содержащий программу на языке С. Проверить эту программу
на соответствие числа открывающих и закрывающих фигурных скобок.
2. Дан символьный файл f. Найти и записать в файл g самое длинное и самое
короткое слово файла f, снабдив их комментариями. При наличии нескольких слов
одинаковой длины вывести их все.
3. Входной файл содержит некоторый текст. Для каждой буквы латинского алфавита
посчитайте, сколько раз она встречается в тексте. Заглавные и строчные буквы
считайте вместе, остальные символы игнорируйте. Программа должна вывести все
буквы латинского алфавита (заглавные, от A до Z, по одной букве в строке), после
этого на этой же строке количество появления этой буквы в исходном тексте.
4. Дан текстовый файл. Записать в другой текстовый файл строку, составленную из
первых символов каждой строки исходного файла.
5. Дан текстовый файл. Найти в этом файле строку, содержащую максимальное число
пробелов, и записать ее в другой файл.
6. Дан текстовый файл. Записать его содержимое в другой файл, поменяв местами
первую строку со второй, третью с четвертой и так далее.
7. Дан текстовый файл. Создать из него два других файла, записав в один из них
строки четной длины исходного файла, в другой – строки нечетной длины. При
отсутствии строк в каком-либо результирующем файле выдать сообщение.
8. Дан текстовый файл и слово, введенное с клавиатуры. Записать в другой текстовый
файл все строки исходного файла, содержащие введенное слово.
Задачи средней сложности
1. Дан текстовый файл f. Переформатировать исходный файл, разделяя его на строки
так, чтобы каждая строка содержала столько символов, сколько содержит самая
короткая строка исходного файла.
2. Сформировать файл на диске, содержащий сведения о наличии билетов на рейсы
авиакомпании. Структурный тип содержит поля: номер рейса, пункт назначения,
время вылета, время прибытия, количество свободных мест в салоне. Написать
программу, которая выбирает необходимую информацию с диска и выводит на
экран:
- время вылета самолетов в город X;
- наличие свободных мест на рейс в город X с временем отправления Y.
3. Сформировать файл на диске, содержащий сведения об ассортименте обуви в
магазине фирмы. Структурный тип содержит поля: артикул, наименование,
количество, стоимость одной пары. Артикул начинается с буквы Д - для дамской
обуви, М - для мужской, П - для детской. Написать программу, которая выбирает
необходимую информацию с диска и выводит на экран:
- сведения о наличии и стоимости обуви артикула X;
9
- ассортиментный список дамской обуви с указанием наименования и имеющегося
в наличии числа пар каждой модели.
4. Сформировать файл на диске, содержащий сведения о нападающих хоккейной
команды. Структурный тип содержит поля: имена нападающих, число
заброшенных ими шайб, число сделанных голевых передач, заработанное
штрафное время. Написать программу, которая выбирает необходимую
информацию с диска и выводит на экран четырех лучших игроков по сумме очков
(голы + передачи)
5. Сформировать файл на диске, содержащий сведения о том, какие из пяти
предлагаемых дисциплин по выбору желает изучать студент. Структурный тип
содержит поля: фамилия студента, номер группы, пять дисциплин, средний балл
успеваемости. Выбираемая дисциплина отмечается символом 1, иначе - 0.
Написать программу, которая выбирает необходимую информацию с диска и
выводит на экран список студентов, желающих прослушать дисциплину X. Если
число желающих превышает 4 человека, то отобрать студентов, имеющих более
высокий средний балл успеваемости
6. Сформировать файл на диске, содержащий сведения об отправлении поездов
дальнего следования с Московского вокзала. Структурный тип содержит поля:
номер поезда, станция назначения, время отправления, время в пути, наличие
билетов. Написать программу, которая выбирает необходимую информацию с
диска и выводит на экран:
- время отравления поездов в город Х во временном интервале от А до В часов;
- наличие билетов на поезд с номером ХХХ.
7. Сформировать массив на диске, содержащий сведения о сотрудниках
Университета. Структурный тип содержит поля: фамилия работающего, название
кафедры, год рождения, стаж работы, должность, оклад. Написать программу,
которая выбирает необходимую информацию с диска и выводит на экран:
- список сотрудников пенсионного возраста на сегодняшний день с указанием
стажа работы;
- средний стаж работающих на кафедре Х.
8. Сформировать массив на диске, содержащий сведения о пациентах глазной
клиники. Структурный тип содержит поля: фамилия пациента, пол, возраст, место
проживания (город), диагноз. Написать программу, которая выбирает
необходимую информацию с диска и выводит на экран:
- количество иногородних, прибывших в поликлинику;
- список пациентов старше Х лет с диагнозом J.
Задачи повышенной сложности
1. Напишите универсальную программу тестирования. Тест, последовательность
вопросов и варианты ответов должны находиться в текстовом файле. Имя файла
теста программа должна получать из командной строки запуска программы.
Количество вопросов теста не ограничено. Вместе с тем, предлагается ввести
следующее ограничение: текст вопроса и альтернативных ответов не должен
занимать более одной строки экрана. Программа должна выставлять оценку по
следующему правилу: ОТЛИЧНО – за правильные ответы на все вопросы,
ХОРОШО – если испытуемый правильно ответил не менее чем на 80% вопросов,
10
УДОВЛЕТВОРИТЕЛЬНО – если правильных ответов более 60%, и ПЛОХО – если
правильных ответов меньше 60%
2. Первая строка входного файла содержит целое число N. Далее (начиная со
следующей строки) идет текст. Необходимо данный текст разбить на строки, длина
которых не превосходит N и вывести его в другой файл. В каждой выведенной
строке не должно быть пробелов в начале строки, пробелов в конце строки, слова в
строке должны разделяться одним пробелом. При этом каждая строка должна быть
максимально длинной, то есть строки формируются по "жадному" принципу:
добавляем слова из входного файла до тех пор, пока длина полученной строки не
превышает N, после этого ставим разрыв строки. Гарантируется, что во входном
файле нет слов длиннее N символов.
3. Решите предыдущую задачу при условии, что текст в выводе программы должен
быть выровнен по ширине: первый и последний символ строки должны быть
непробельными, при необходимости между словами должны быть добавлены
дополнительные пробелы, при этом количество пробелов между любыми двумя
словами в одной строке не должно отличаться более, чем на 1. Последняя строка в
выводе должна быть выровнена по левому краю.
4. Задан текстовый файл. Упорядочить его строки по возрастанию длины, принимая
во внимание, что количество строк файла может быть очень большим и полностью
считать его в оперативную память не представляется возможным.
5. Разработать набор функций для работы с конфигурационным файлом программы.
Функции должны обеспечивать сохранение в файл значения параметра с заданным
именем, а также считывания значения параметра из файла по его имени.
Предусмотреть возможность сохранения и считывания параметров различных
типов, включая числовые, символьные и строковые. Для строковых типов
предусмотреть возможность сохранения и считывания многострочных значений и
значений, содержащих символ ‘=’.
6. Разработать набор функций, обеспечивающих сохранение в тестовый файл
значений числовых массивов в формате, аналогичном XML. Формат файла и состав
атрибутов для хранения массивов разработать самостоятельно.
7. Разработать набор функций, обеспечивающих разбор XML-файла для считывания
из него для узла, заданного своим именем, значения атрибута, также заданного
своим именем.
8. Разработать набор функций, обеспечивающих разбор HTML-файла, включающего
описание таблицы, содержащей числовые значения. Функции должны
обеспечивать считывание значения в заданной номером строки и номером столбца
ячейки таблицы, а также запись в заданную ячейку нового значения. В последнем
случае должен формироваться новый текстовый файл.
9. Заданы два текстовых файла. Найти в этих файлах максимальный по длине
совпадающий фрагмент. Принять во внимание, что размеры файлов могут быть
такими, что полное считывание их содержимого в оперативную память не
представляется возможным
11
Download