Тема Цель :

advertisement
Лабораторные работы №3,4
Тема: Потоковый ввод-вывод в языке Си.
Цель: Изучение принципов работы с файлами через механизм потокового
ввода-вывода на языке Си, приобретение практических навыков работы с
файлами в Си.
1. Методические указания
Интерактивность программ является одним из основополагающих
принципов работы: они взаимодействуют с пользователем, принимая от него
данных и команды, а результаты выводят на экран монитора. Еще одним
важным требованием к программам можно назвать необходимость сохранять
результаты вычислений в энергонезависимой памяти (в файлах на дисках),
чтобы с ним можно было обратиться в следующих сеансах работы с программой. В языке Си и работа с консолью, и работа с файлами на диске реализуется в едином программном интерфейсе, называемом потоковый вводвывод. Такой подход к взаимодействию программы с устройствами вводавывода характерен для работы в текстовом режиме в таких операционных
системах, как Microsoft Windows (и более ранней MS DOS) и всех "клонах" UNIX .
В чем заключается принцип потокового ввода-вывода?
Вся работа с внешним устройством заменяется с точки зрения прикладной программы работой со специальной программной абстракцией – потоком.
1. Операционная система создает в памяти поток, который аккумулирует
все данные, перенаправляемые из программы в файл или на устройства,
либо читаемые с файла или устройства.
2. Программа для взаимодействия с файлом или устройством, обращается
к связанному с ним потоку. Во внутренние буферы потока записывается
вся передаваемая информация;
3. За взаимодействия потока с файлом (устройством) отвечает сама операционная система. Особенности работы устройства (используемые
драйвера, типы устройств, режимы их работы) прикладного программиста уже не касаются.
Подобный подход упрощает взаимодействие программиста с внешними
устройствами, позволяя использовать одни и те же подходы как для вывода
информации на экран монитора, так и для записи информации в файл. Поток
выступает своеобразным информационным трубопроводом, соединяющим
программу с внешним устройством. Программист ”заливает” информацию в
свой конец этого трубопровода и может быть уверен, что она дойдет до адресата. Концепцию потока как информационного трубопровода иллюстрирует
рис. 1.
поток
п
о
т
о
к
п
о
т
о
к
поток
Рисунок 1. Взаимодействие программы и внешних устройств с помощью потока.
В языке Си потоковый ввод-вывод реализуется с помощью средств библиотеки stdio.h. В ней определен целый ряд типов данных, констант, макросов и
методов, которые позволяют создавать и закрывать потоки, читать и писать
в/из них информацию, отслеживать и изменять состояние потока. Поток описывается в этой библиотеке структурой FILE. Структура FILE содержатся
компоненты, с помощью которых ведется работа с потоком, в частности: указатель на буфер, указатель (индикатор) текущей позиции в потоке, дескриптор потока и другая информация. В среде MS Visual Studio структура FILE
определена следующим образом:
struct _iobuf {
char *_ptr;
int
_cnt;
char *_base;
int
_flag;
int
_file;
int
_charbuf;
int
_bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
Однако, работать напрямую с полями структуры FILE программисту не
приходится. При создании нового потока для него создается экземпляр
структуры FILE, адрес которой можно использовать в сервисных функция
библиотеки stdio для манипуляций с файлом. Задача программиста - запомнить адрес в переменной и передавать эту переменную в функции для чтения, записи, диагностики состояния потока.
Переменная-указатель на структуру FILE называется потоковой переменной, и ее определение всегда предшествует работе с потоковыми функциями:
FILE * fp; //потоковая переменная
Прежде чем говорить о создании новых потоков, стоит упомянуть о
стандартных потоках операционной системы. В операционной системе всегда существуют и никогда не удаляются следующие стандартные потоки:
- Стандартный поток ввода (обозначение: stdin в языке Си, cin – в
Си++) - используется для ввода символьных данных в программу. Поумолчанию этот поток связан с клавиатурой компьютера;
- Стандартный поток вывода (обозначение: stdout в языке Си, cout – в
Си++ ) - используется для вывода символьной информации Поумолчанию этот поток связан экраном дисплея;
- Стандартный поток ошибок (обозначение: stderr в языке Си, cerr – в
Си++) - используется для вывода сообщений о возникших ошибках и
предупреждениях при работе программы, По-умолчанию этот поток
закреплён за экраном дисплея; Наличие двух потоков по умолчанию
связанных с экраном дисплея связано с тем, что имеется возможность
переопределить потоки, связав их с другим устройством (функцией
freopen). Тогда, определив для потока stderr в качестве устройства вывода файл на диске, все данные, направляемых в поток, можно перенаправить с экрана в этот файл и не смешивать на экране полезную информацию с диагностической.
- Стандартный поток печати (обозначение: stdprn в языке Си.) - используется для вывода результатов работы программы на печать. Поумолчанию этот поток закреплён за текущим принтером в системе,
подключённым к порту LPT1.
Стандартные потоки имеют тип FILE*, что позволяет использовать их
в качестве параметров функций работы с потоками, которые будут перечислены ниже.
Все остальные потоки программист должен создавать сам. Они создаются
уничтожаются с помощью функций открытия и закрытия файлов, на период
чтения/записи/добавления информации в эти файлы.
Открытие потока
Для взаимодействия с устройством ввода/вывода программист должен
создать новый поток и связать его с целевым устройством и осуществлять
операции ввода/вывода работая с потоком.
Для создания потока предназначена функция fopen:
FILE*fopen(char*filename,char*mode);
Первый параметр функции задает имя открываемого файла (возможно,
с относительным или абсолютным путем к нему).
Режим открытия потока mode задается в виде строки с предопределенным содержимым. Состоит из 2-х частей:
1)Режим доступа, определяющий, какие операции можно совершать с
содержимым потока
2)Тип потока, определяющий, как интерпретировать содержимое потока: в виде совокупности текстовых строк (текстовый поток, символ 't') или
как двоичную информацию (двоичный поток, символ 'b').
Для указания режима доступа используются английские буквы 'r’, ‘w’,
‘a’:
r–открыть для чтения(файл должен существовать)
w–режим открытия на запись(если файл не существует то создается,
если существует, то обнуляется)
a–открытие потока на дозапись(файл должен существовать)
r+–открытие существующего файла на чтение и запись
w+–открытие файла на чтение и запись(если файл не существует то создается, если существует, то обнуляется)
a+ - открытие файла на чтение и добавление(если файл не существует
то создается, если существует, то откроется с сохранением содержимого).
Примеры вызова функции fopen:
fopen(”file.dat”, “r”);
fopen(”c:\\tmp\\log.txt”, “w+”); //или так:
fopen(”c:/tmp/log.txt”, “w+”);
fopen(”..\\.my.sav”, “r+b”);
Функция fopen создает поток и возвращает указатель на связанную с
ним структуру FILE как результат своей работы. Если открыть поток не удалось (указано неверное имя файла, невозможно открыть в указанном режиме,
открыто слишком много файлов, у пользователя нет прав на открытие файла
и другие причины), то fopen вернёт нулевой указатель NULL и работать с потоком нельзя. Код ошибки открытия можно получить вызовом макроса errno.
Пример открытия нового потока:
FILE*fp;
if((fp=fopen(“tmp.dat”,”w+”))==NULL)
printf(“Ошибка открытия”);
else
{//работа с потоком с использованием fp
…
fclose(fp);
}
Закрыть ранее открытый поток можно с использованием функции
fclose, передав ей в качестве параметра потоковую переменную:
fclose(fp);
Закрыть все открытые потоки можно функцией fcloseall().
Функции для работы с потоком.
После открытия потока можно осуществлять операции ввода-вывода,
считывая и записывая информацию в файл (устройства), связанное с потоком. Простейшим вариантом будет посимвольный ввод-вывод с использованием функций:
int fputc(int c, FILE *stream) - выводит символ с в поток
stream.
int fgetc(FILE * stream ) – читает очередной символ из потока stream.
Рассмотрим пример посимвольного ввода-вывода информации из/в поток:
//Листинг 1. Пример посимвольного чтения/записи данных из/в поток
char str[6]=“hello”, ch;
FILE * fp;
if((fp = fopen(“file.dat”,”w+”))==NULL)
printf(“Ошибка открытия потока”);
else
{
int i=0;
while (str[i]) fputc(str[i++], fp);
fseek(fp, 0, SEEK_SET);
while(feof(fp)==0)
{ ch=fgetc(fp);
fputc(ch, stdout);
}
fclose (fp);
}
В примере, рассмотренном в листинге 1, содержимое строки str сначала
посимвольно выводится в поток, связанный с файлом file.dat, а затем считывается содержимое потока и выводится на экран через поток stdin.
Помимо непосредственно функций обмена данными с потоками (fputc,
fgetc), в примере использованы не встречавшиеся до сих пор функции (fseek,
feof). Эти вспомогательные функции позволяют следить за состоянием потока и изменять его. Обсудим их подробнее.
Позиционирование в потоке
Одной из характеристик потока является позиция чтения (записи). Фактически, она представляет собой простой счетчик байтов относительно начала файла. Именно в эту позицию помещаются даны при очередной попытке
записи, из позиции чтения извлекаются данные при чтения информации.
Позиция текущего указателя потока изменяется при выполнении операций чтения записи, перемещаясь на число, равное количеству записанных
(считанных) байт. При дисковом вводе/выводе позиция может меняться произвольно. Потоки, поддерживающие смену позиции называются потоками с
произвольным доступом. Следующие функции, позволяющие осуществлять
произвольный доступ:
long int ftell (FILE *stream);
Функция ftell возвращает текущую позицию указателя в файле. Она
может завершиться с ошибкой, если файл не поддерживает позиционирование или если позиция не может быть представлена (не помещается) как long.
В случае ошибки функция ftell возвращает -1. Еще один способ получить
значения указателя потока – использовать функцию
int fgetpos(FILE *fp, fpos_t *pos);
Переместить позицию текущего указателя можно с использованием
функции:
int fseek (FILE *stream, long int offset, int base);
Здесь stream – потоковая переменная. а параметры offset и base задают
новое значение указателя: он устанавливается по смещению offset байт относительно позиции base, в качестве которой можно задать:
SEEK_SET-начало потока
SEEK_END-конец потока
SEEK_CUR-текущее положение.
Тогда вызов
fseek(fp, 0, SEEK_SET);
переместит указатель в начало потока, а вызов
fseek(fp, -4, SEEK_CUR);
возвратит его на 4 байта назад относительно текущей позиции.
На позицию указателя могут повлиять функции:
int fsetpos(FILE*fp, const fpos_t *pos);
void rewind(FILE *fp); //перемещает указатель к началу потока
Мониторинг состояния потока.
Размер потока (файла) не всегда известен заранее, поэтому встает проблема идентификации того момента, когда в процессе чтения данных будет
достигнет конец потока. Для решения этой проблемы лучше всего использовать макрос
int feof(FILE *stream)
Он возвращает ненулевое значение в тот момент, когда при чтении
данных из потока будет прочитан символ конца потока (EOF).
Такой принцип работы макроса делает очень популярным следующий
цикл чтения данных из потока:
while( !feof(fp) ) //цикл пока не достигнут конец потока
{ // чтение из потока fp }
Так, например, можно посимвольно прочитать и вывести на экран все
содержимое потока:
int c;
while ( (c = fgetc(fp)) != EOF )
{ putchar(c); }
В структуре FILE фиксируются ошибки, произошедшие при взаимодействии с потоком. Определить, что операции с потоком привели к ошибке
можно с использованием макроса ferror(FILE*stream). Она возвращает ненулевое значение если возникла ошибка при работе с потоком. Код
ошибки можно получить с помощью макроса errno.
FILE *pFile; char ch=’!’;
pFile = fopen ("d:\\myfile.txt","r");
fwrite(&ch, 1,1, pFile);
if(ferror(pFile))
printf("Произошла ошибка. Её код: %d", errno);
В рассмотренном пример на экране появится сообщение:
Произошла ошибка. Её код: 9
Если посмотреть содержимое файла errno.h, то можно убедиться, что
код 9 соответствует макроопределению
#define EBADF
9
В данном случае, ошибка, очевидно, возникла из-за нарушения режима
доступа: была предпринята попытка записать информацию в поток, открытый только на чтение.
Необходимо также отметить, что функция ferror реагирует на индикатор ошибки, устанавливающийся в структуре FILE в момент ее возникновения. Сама функция ferror этот индикатор не сбрасывает и повторный вызов
ferror также обнаружит ошибку. Чтобы сбросить индикатор ошибки, необходимо вызвать функцию clearerr(FILE * stream).
Осуществлять ввод-вывод посимвольно зачастую будет нерационально, эффективнее считывать информацию из потока или помещать ее в поток
более крупными блоками. Для текстовой информации можно оперировать
строками с использованием функций:
сhar * fgets ( char *s, int n, FILE * stream) для
чтения данных (где s – строка, в которую считываются данные из потока
stream, n – максимальное количество считываемых символов). Данные считываются в строку начиная с позиции указателя потока и до конца строки в
файле (первого символа '\n'). Параметр n необходим для предотвращения
случаев переполнения строки s, когда объем считанных из файла данных
превысит выделенный под строку участок памяти. Функция возвращает указатель на считанную строку.
int fputs ( char *s, FILE * fp) для записи содержимого
строки s в поток. Функция возвращает количество записанных в поток символов.
Пример в листинге 2 иллюстрирует использование функций чтения и
записи строки в поток.
//Листинг 2. Удалить из файла каждую вторую строку
FILE * fp, *fp2;
int i=0;
char filename[30], str[256];
fgets(filename, 30, stdin);
fp=fopen(filename,“r+”);
if(fp!=NULL)
{ fp2=fopen(“temp.txt”, “w+”);
while(!feof (fp) )
{ fgets(str, 256, fp); //Программа обрабатывает файлы
//со строками длиной до 255 символов
i++;
if(i==2)
{fputs(str, fp2); i=0;}
}
fclose(fp);fclose(fp2);
remove(filename);
rename(“temp.txt”, filename);
}
Для вывода в поток информации произвольного типа используется
функция fprintf.
int fprintf( FILE * stream, const char * format, ... );
Эта функция выполняет форматированный вывод в поток. Она записывает в указанный поток stream последовательность символов в формате,
указанном аргументом format. После параметра format функции необходимо
указать список объектов, значения которых будут выведены в поток.
Параметры функции:
 stream
Указатель на объект типа FILE, который связан с потоком (потоковая
переменная).
 format
Строка форматов, содержащая текст, который будет выведен в поток. Опционально, строка может содержать встроенные метки форматирования (спецификации вывода), которые заменяются значениями,
указанными в последующих дополнительных аргументах и отформатированы требуемым образом.
Форматируемые теги должны следовать следующему формату записи:
%[флаги][ширина поля][.точность][длина]спецификатор
Спецификаторы задают тип выводимых данных и способ из представления.
Ниже приведены спецификаторы форматирования потоков ввода/вывода:
Таблица 1. Спецификаторы вывода функции fprintf
Спецификатор Описание
c
Символ
d или i
Знаковое десятичное число.
e
Экспоненциальная форма записи чисел (мантисса/экспонента) с использованием символаe.
E
Экспоненциальная
форма
записи
чисел (мантисса/экспонента) с использованием символаE.
f
Десятичное значение с плавающей точкой.
g
Используется для обозначения короткого %e или %f
G
Используется для обозначения короткого %E или %f
o
Восьмеричная система счисления
s
Строка символов.
u
Использовать беззнаковое целое десятичное значение.
x
Использовать беззнаковые шестнадцатеричные значения
X
Шестнадцатеричные целые без знака (прописные
буквы)
p
Указатель на адрес
n
Запись по указателю, переданному в качестве аргумента, количества символов, записанных на момент
появления командной последовательности, содержащей n
%
Вывод символа %
Форматирующие теги могут также содержать флаги, ширину, точность и модификаторы, которые являются необязательными:
Таблица 2. Флаги вывода функции fprintf
Флаг
Описание
-
Выравнивание по левому краю выделенного поля. По умолчанию стоит правостороннее выравнивание в выделенном поле.
+
Использовать знаки плюс или минус для вывода значений. По
умолчанию выводится только знак минус, если значение отрицательное.
(space) Если в строке есть символ пробела, он не игнорируется, а попадает в поток вывода.
#
Используется
вместе
со
спецификаторами o, x или X специфицирующих значение отличное от нуля, 0x или0X.
Используется вместе со спецификаторами e, E и f, принудительно выводит десятичную точку, даже, если у значения нет
цифр в дробной части. По умолчанию, если нет цифр в дробной
части,
десятичная
точка
не
отображается.
Используется с a, A, e, E, f, F, g или G результат такой же, как
и со спецификаторами е или Е, но нули не удаляются.
0
Левая часть после числа заполняется нулями (0) вместо пробелов, если ширина поля больше числа.
Таблица 3. Ширина вывода для функции fprintf
ширина Описание
number
Минимальное количество символов для печати. Если значение для печати короче, чем это число, пустые места заполняются пробелами. Значение не обрезается, даже если оно
больше number.
*
Ширина не указана в строке формата, но в качестве дополнительного аргумента целое значение предшествующего тому
аргументу, который должен быть отформатирован.
Таблица 4. Точность вывода функции fprintf
.точность Описание
.number
Для
спецификаторов
целых
чисел d, i, o, u, x, X: указывает минимальное количество
цифр. Если значение, короче, чем это число, пустое место
заполняется нулями. Значение не обрезается, даже если
number меньше, чем длина выводимого объекта.
Для спецификаторов е, Е и F: это количество цифр, которое будет напечатано после десятичной точки.
Для спецификаторов g и G это максимальное количество
значащих цифр для печати.
Для спецификатора s - это максимальное количество символов для печати.
Для c
этот
параметр
не
имеет
значения.
При отсутствии этого параметра, точность по умолчанию
равна 1.
.*
Точность не указана в строке формата, но в качестве дополнительного аргумента передается целое значение, оно
должно предшествовать форматируемому аргументу.
Таблица 5. Длина вывода функции fprintf
длина Описание
h
short int или unsigned short int (применяется только к целым
спецификаторам: i, d, o, u, x иX).
l
long int или unsigned long int в случае применения к целым спецификаторам (i, d, o, u, x и X), а также к широким символам
или строкам с широкими символами, для спецификаторов c и s.
L
Long double в случае применения к спецификаторам вещественных типов %e, %E, %f, %F, %g, %G)
В случае успешного вывода функция fprintf возвращает общее число
записанных символов. В случае неудачи, возвращается отрицательное число.
Рассмотрим пример форматированного вывода информации в поток
//Листинг 2. Запись данных в поток функцией fprintf
#include <stdio.h>
#include <windows.h>
int main(int argc, _TCHAR* argv[])
{
SetConsoleCP(1251);
SetConsoleOutputCP(1251);
int n, age;
char name[20];
FILE* pFile = fopen ("d:\\myfile.txt","w");
for (n=0 ; n<5 ; n++)
{
puts ("Введите имя: ");
gets (name);
puts ("Введите возраст: ");
scanf("%d", &age);
fflush(stdin);
fprintf (pFile, "%2d. Имя [%-20.20s] Возраст [%3d]\n",n,name, age);
}
fclose (pFile);
return 0;
}
В приведенном примере для функции fprintf использованы три спецификатора вывода – для вывода порядкового номера %2d (целое число, под
вывод отводится 2 позиции), имени %-20.20s (строка, выровненная по левому краю, под вывод отводится минимум 20 позиций, максимально выводится
20 символов) и возраст %-3d (целое число, под вывод отводится 3 позиции,
вывод выравнивается по левой границе). В результате в файле получим следующее содержимое:
0.
1.
2.
3.
4.
Имя
Имя
Имя
Имя
Имя
[Иван Иванов
[Ольга Александрова
[Петр Петров
[Мария Сидорова
[Егор Егоров
]
]
]
]
]
Возраст
Возраст
Возраст
Возраст
Возраст
[23
[19
[38
[29
[46
]
]
]
]
]
Для чтения форматированных данных из потока необходимо использовать функцию fscanf.
int fscanf (FILE * stream, char * format_string, …);
где stream - потоковая переменная,
format_string - строка управления форматом.
Функция fscanf считывает данные из текущей позиции потока stream в место,
определяемое заданием аргументов, указанных в вызове после строки форматов. Каждый аргумент должен быть указателем на переменную типа, соответствующего типу, заданному в спецификации ввода строки формата.
Спецификация ввода имеет следующий формат:
%[*][ширина][длина]спецификатор
Спецификаторы для ввода совпадают с указанным в таблице 1. Например, чтобы считать из потока данные, записанные в примере из листинге 2,
необходимо вызвать функцию fscanf следующим образом:
pFile = fopen ("d:\\myfile.txt","r");
while(!feof(pFile))
{ fscanf (pFile, "%2d. Имя [%20s ] Возраст [%d ]\n",&n, name,
&age);
printf("Номер: %2d Имя %s Возраст %d\n", n, name, age);
}
Использование функции fscanf для чтения данных сложного формата
может приводить к проблемам интрепретации считанных данных, поскольку
в качестве разделителя считываемых объектов используются пробел и про-
бельные символы. Необходимо строго следить за соответствием формата записи (в fprintf) и считывания (в fscanf).
Ввод-вывод двоичных данных проще всего реализовать с использованием функций чтения-записи с использованием буфера fread и fwrite. Эти
функции позволяют читать и записывать блоки данных любого типа. Рассмотрим прототипы этих функций:
size_t fread (void * buffer, size_t size,
size_t count, FILE * fp);
size_t fwrite (const void * buffer, size_t size,
size_t count, FILE * fp);
buffer– указатель на область памяти, в которую будут прочитаны данные из потока (для fread) или из которой будут выводиться данные в поток
(для fwrite).
count — счетчик, определяющий, сколько считывается и записывается
блоков данных,
size - задает размера блока считываемого/записываемого блока данных.
fp – потоковая переменная, связанная с потоком, из которого/в который
читаются/пишутся данные.
Функция fread возвращает количество прочитанных блоков. Если прочитать данные в запрошенном объеме не удалось ( достигнут конец файла
или произошла ошибка), то функция может вернуть значение, меньшее, чем
значение счетчика. Аналогично, функция fwrite возвращает количество записанных блоков данных и возвращаемый результат будет равен значению
счетчика при вызове, если не произошло ошибок. Функции fread() и fwrite()
можно адаптировать для записи объектов различных типов данных. Можно
записать в файл значение переменной:
Вывод с использованием fwrite
int x=142822;
fwrite(&x, 1, sizeof(int), fp);
Содержимое файла после вывода:
двоичное
Значение переменной займет в файле
4 байта (по размеру переменной)
Форматированный вывод fprintf
double x=142822;
fprintf(fp, “%d”, x);
Содержимое файла после вывода: тестовое
Значение переменной займет в файле
6 байта (по количеству цифр)
Если необходимо вывести в файл содержимое массива, функция fwrite
позволяет обойтись без использования цикла:
Вывод с использованием fwrite
Форматированный вывод fprintf
const int N=10;
double mas[N];
…//инициализация массива
fwrite(mas, N, sizeof(double), fp);
const int N=10;
double mas[N];
…//инициализация массива
for(int i=0; i<N; i++)
fprintf(fp,
“mas[%d]=%f”,
i,mas[i]);
При выводе (вводе) структурной переменной нет необходимости отдельно записывать (считывать) каждое поле:
Вывод с использованием fwrite
Форматированный вывод fprintf
struct MyStruct
{
int field1;
char field2[40];
double field[3];
} m;
fread(&m, 1, sizeof(MyStruct), fp);
struct MyStruct
{
int field1;
char field2[40];
double field[3];
} m;
fscanf(fp, “%d”, &m.field1);
fscanf(fp, “%s”, m.field2);
for(int i=0; i<3; i++)
fscanf(fp, “%f”, &m.field3[i]);
Еще одно достоинство fwrite/fread перед fscanf/fprintf – в скорости выполнения операций. Функции форматированного ввода-вывода перед непосредственно перемещением данных форматируют в соответствии с заданной
стройкой форматов, а это требует дополнительного времени. При использовании fwrite/fread форматирования данных не производится, операции выполняются быстрее.
Язык С++ также поддерживает концепцию объектно-ориентированного
ввода-вывода, но потоки здесь представлены в виде объектов классов библиотеки iostream:
ifsteram - для ввода из файла;
ofsteram - для вывода в файл;
fsteram - для обмена с файлом в двух направлениях.
При этом стандартные потоки представлены объектами:
cin – стандартный поток ввода;
cout – стандартный поток вывода;
cerr – стандартный поток приема сообщений об ошибках.
Принцип работы с потоками аналогичен принятому в языке Си - программист создает объект-поток как экземпляр требуемого класса, связывая
его с файлом на диске, после чего используя сервисные функции. Для чтения
данных их потока можно использовать перегруженную операцию >>, для записи информации в поток используется операция <<. Принципиальное отличие объектно-ориентированного подхода Си++ заключается в том, что весь
функционал работы с потоком сосредоточен в методах классов, которые
должны вызываться в контексте объекта-потока. В листинге 4 приведен пример обмена данными программы с файлом на языке Си++.
#include <fstream.h>
…
fstream f; char str[20];
f.open("temp",ios::in|ios::out); //открываем поток на
//чтение и запись
f.write(“Тест”,4); //пишем в поток слово Тест
f.seekg(0);
//перемещаем указатель потока в его начало
f>>str;
//читаем строку из потока
cout<<str;
Таким образом, эффективное использование потокового ввода-вывода
на языке Си++ должно опираться на знание методов и компонентных данных
классов-потоков. Как видно из примера, названия методов во многом схожи с
названиями функции библиотеки Си и разобраться в их назначении и способах применения будет совсем несложно. Более подробную информацию о
потоковых классах языка Си++ можно почерпнуть в [1].
Контрольные вопросы
1. Что такое поток? Какие преимущества дает потоковый подход к вводувыводу информации по внешние устройства?
2. Какие стандартные потоки вам известны? С какими устройствами они
связаны по умолчанию?
3. Как создать собственный поток в программе? Какие режимы доступа к
потокам вам известны?
4. Как осуществляется символьный ввод-вывод в библиотеке stdio?
5. Что такое текущий указатель потока? Какую роль он играет? Как
узнать/изменить позицию указателя?
6. Для чего предназначен макрос feof? Приведите пример его использования..
7. Каким образом можно идентифицировать ошибки при работе с потоком?
8. Проведите сравнительный анализ возможностей вывода массива
структур с использованием функций fwrite и fprintf.
9. Какие средства ввода-вывода предоставляет программисту язык
Си++?
Порядок выполнения работы
1. Ознакомьтесь с теоретическими основами потокового ввода-вывода на
языке Си в настоящих указаниях и конспектах лекций.
2. Получите вариант задания у преподавателя.
3. Составьте алгоритм решения задачи согласно варианту задания, оформите его в графической форме.
4. Используя разработанный алгоритм, напишите программу.
5. Отладьте разработанную программу и покажите результаты работы
программы преподавателю.
6. Составьте отчет по лабораторной работе.
7. Отчитайте работу преподавателю.
Содержание отчета
Отчет по лабораторной работе должен содержать следующие сведения:
- название и цель работы;
- вариант задания;
- графическую схему алгоритма решения задачи;
- листинг разработанной программы с комментариями;
- результаты работы программы.
Задание к лабораторной работе №3
Для задачи из лабораторной работы №1 или №2, полученной ранее, реализовать возможность сохранения введенных пользователем данных (содержимого массива структур или списка структур) в файл на диске. Результаты
обработки данных также сохранять в файле. При запуске программы предоставлять пользователю выбор – будет он вводить новые данные, или считает
данные из файла (название файла должно вводиться пользователем).
Пример выполнения задания к лабораторной работе №4:
Записать в файл несколько структур типа "Книга". Удалить из файла все
книги, изданные до указанного года.
#include <stdio.h>
#include <conio.h>
#include <windows.h>
struct book
{
char title[20],author[10];
int year,pages;
};
int main()
{
SetConsoleCP(1251);
SetConsoleOutputCP(1251);
char filename[30], ch;
int i=0,n, delYear;
FILE *fp,*temp;
book b,tempb;
printf("Получить данные из файла (1) или с клавиатуры (2)? ");
do
{ch=getche();
}
while(ch!='1' && ch!='2');
temp=fopen("temp","w");
int x=sizeof(book);
if(ch=='2')
{
printf("\nСколько книг?");
scanf("%d",&n);
printf("Название файла для сохранения списка книг?");
scanf("%s",&filename);
fp=fopen(filename,"w+");
if(fp==NULL)
{ printf("Не могу открыть указанный файл");
getch();
return 1;
}
for(i=0;i<n;i++)
{
printf("Введите информацию о книге № %d (название, автор, год издания,
кол-во страниц)",i+1);
scanf("%s %s %d %d",b.title,b.author,&b.year,&b.pages);
fwrite(&b,x,1,fp);
}
}
if(ch=='1')
{
printf("\nВ каком файле сохранен список книг? ");
scanf("%s",&filename);
fp=fopen(filename,"r+");
if(fp==NULL)
{ printf("Не могу открыть указанный файл");
getch();
return 1;
}
}
printf("\nКниги до какого года издания удалять? ");
scanf("%d", &delYear);
printf("\nВ файле остаются книги:\n");
fseek(fp,0,SEEK_SET);
i=1;
while(1)
{
fread(&b,sizeof(book),1,fp);
if (feof(fp)) break;
if(b.year>delYear)
{
printf("%d) Название: %15s, автор: %10s, год издания: %4d\n", i,
b.title, b.author, b.year);
fwrite(&b,sizeof(book),1,temp);
i++;
}
}
getch();
fclose(fp);
fclose(temp);
remove(filename);
rename("temp",filename);
}
Варианты заданий к лабораторной работе №4.
Вариант №1
Написать программу, удаляет из текстового файла предложения, содержащие максимальное количество знаков пунктуации.
Вариант №2
Написать программу, которая заменяет в текстовом файле порядок следования
предложений на обратный.
Вариант №3
Написать программу, в текстовом файле находит и удаляет все вопросительные предложения.
Вариант №4
Написать программу, которая считывает текст из файла и выводит его на экран, заменяя при необходимости первую букву всех предложений на заглавную.
Вариант №5
Написать программу, которая удаляет из текстового файла предложения, в которых
встречаются цифры.
Вариант №6
Написать программу, которая удаляет из текстового файла слова, начинающиеся и
оканчивающиеся на гласные буквы.
Вариант №7
Написать программу, удаляет из текстового файла предложения, состоящие из заданного количества слов.
Вариант №8
Написать программу, которая переформатирует содержимое текстового файла,
формируя из каждого предложения отдельный абзац с абзацным отступом 4 символа и задаваемой с клавиатуры шириной строки n.
Вариант №9
Реализовать в программе функцию замену в текстовом файле заданной строки на
другую строку, также задаваемую с клавиатуры.
Вариант №10
Написать программу, которая в текстовом файле заменяет все комментарии, заданные
в круглых скобках, на одиночный пробел.
Вариант №11
Написать программу, которая в текстовом файле находит самое короткое предложение
и переносит его в конец текста.
Вариант №12
Написать программу, которая в текстовом файле меняет местами два соседних слова.
Вариант №13
Написать программу, которая считывает текст из файла и преобразует его таким образом, чтобы каждая строка имела размер не более n символов (n и имя файла задаются
с клавиатуры)
Вариант №14
Написать программу, которая разбивает содержимое файла на n равных частей, каждую сохраняя в отдельный файл. Имя файла и число n вводится.
Вариант №15
Написать программу, которая в текстовом файле находит самое короткое предложение
и удаляет его
Вариант №16
Написать программу, которая форматирует содержимое текстового файла по ширине
самой длинной строки, добавляя пробелы между словами в остальных строках
Вариант №17
Написать программу, которая осуществляет поиск заданной подстроки в текстовом
файле и заменяет в файле каждое вхождение на другую подстроку.
Вариант №18
Написать программу, которая в текстовом файле меняет местами соседние предложения.
Вариант №19
Написать программу, которая в текстовом файле удаляет все повествовалтельные
предложения, не сбалансированные по скобкам (когда количество открывающихся не
равно количеству закрывающихся).
Вариант №20
Написать программу, которая в текстовом файле находит и удаляет рядом стоящие
одинаковые слова.
Пример выполнения задания к лабораторной работе №4:
На диске имеется текстовый файл с произвольным именем. В файл words.txt вывести все слова из заданного количества букв (количество букв в слове вводится), каждое в
отдельной строке.
Текст программы:
#include<stdio.h>
#include<conio.h>
#include<string.h>
#include<windows.h>
int main()
{
SetConsoleCP(1251);
SetConsoleOutputCP(1251);
int wordLength,x=0,i,j=0;
char fileName[20];
printf("Ведите длину слова");
scanf("%d",&wordLength);
printf("Ведите название файла");
scanf("%s",fileName);
FILE *A,*f;
//открываем исходный поток
if (f=fopen(fileName,"r"))
{ char s[100];
A=fopen("d:\\words.txt","w+");
//пока не считаем все строки из потока
while(!feof(f))
{
//считывем строку из потока
fgets(s,100,f);
x=0; //количество символов в очередном слове
i=0;
//пропускаем пробелы и знаки препинания в начале строки
while(s[i]==' '||s[i]==','||s[i]=='\n') i++;
for (;i<=strlen(s);i++)
{
//если очередной символ строки – не пробел, запятая, конец строки –
//увеличиваем количество символов в слове
if (s[i]!=' ' && s[i]!=',' && s[i]!='\n') x++;
else
{
//нашли символ, заканчмвающий очередное слово
if (x==wordLength) //если символов в слове насчитали wordLength
{
//записываем все символы слова в файл words.txt
for(j=i-x;j<i;j++)
fprintf(A,"%c",s[j]);
fprintf(A,"\n");j=0;
}
//пропускаем дублирующиеся пробелы и запятые после очередного слова
while(s[i]==' '||s[i]==',' || s[i]=='\n') i++;
x=1;
}
}
//выводим строку на экран
puts(s);
}
}
else puts("Ошибка открытия файла");
getch(); return 0;
}
Начало
Ввод wordLength, fileName
f=fopen(fileName,"r")
a=fopen(“words.txt”,"w+”)
да
конец потока f ?
нет
Считываем строку s из входного
потока
x=0, i=0
нет
s[i]=’ ‘ || s[i]=’,‘ || s[i]=’\n‘
да
i++
Вывод сообщения об
ошибке
i<strlen(s)
нет
s[i]!=' ' && s[i]!=','
&& s[i]!='\n'
да
x++
нет
s==wordLenth
да
Вывод s[i-x].s[i] в поток a
j=0
s[i]=’ ‘ || s[i]=’,‘ || s[i]=’\n‘
да
i++
x=1
i++
Вывод s на экран
Конец
нет
Дополнительный пример .
Переформатировать файл таким образом, чтобы длина каждой строки было ровно n символов (n вводится)
#include <stdio.h>
#include <conio.h>
#include <iostream>
using namespace std;
int main()
{
char filename[30],c;
int i=0,n;
FILE *fp,*temp;
cout<<"Enter file name";
cin>>filename;
cout<<"Maximium length of line";
cin>>n;
fp=fopen(filename,"r");
if(fp==NULL)
{
cout<<"Bad file name";
return 1;
}
temp=fopen("temp","w");
while(!feof(fp))
{
c=fgetc(fp);
if (c!='\n')
{
i++;
if (i>n)
{fputc('\n',temp);
i=0;
}
fputc(c,temp);
}
}
fclose(fp);
fclose(temp);
remove(filename);
rename("temp",filename);
getch();
}
Литература
1. Березин Б. И., Березин С. Б. Начальный курс С и С++. М.: ДиалогМИФИ, 2007 г., - 288с.
2. Борисенко В.В. Основы программирования. М.: Интернет-университет
информационных технологий, 2009 г., - 328с.
3. Гагарина Л. Г., Колдаев В. Д. Алгоритмы и структуры данных.
М.:Финансы и статистика, 2009 г., - 304 с.
4. Мюссер Д., Дердж Ж., Сейни А. С++ и STL: справочное руководство.
М.: ООО «И.Д. Вильямс», 2010 г., - 432с.
5. Страуструп Б. Язык программирования С++. Специальное издание.
СПб.: Бином, 2008 г., 1104с.
6. Фомин С.С. Подбельский В.В. Программирование на языке Си: Учебное пособие. М.:Финансы и статистика, 2007 г. – 600с.
Download