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

2vcTnguyvU

.pdf
Скачиваний:
6
Добавлен:
15.04.2023
Размер:
955.27 Кб
Скачать

Но это неявно делает деструктор потока, так что явный вызов close() может понадобиться, если только файл нужно закрыть до достижения конца области определенности потока.

Выполнение операций чтения и записи

Чтобы читать и писать массивы и структуры, для этого можно использовать функции read и write. При использовании функций read и write нужно указать буфер данных, в который данные будут читаться или из которого они будут записываться, а также длину буфера в байтах, как показано ниже: input_file.read(buffer, sizeof(buffer)) ; output_file.write(buffer, sizeof(buffer));

Например, следующая программа использует функцию write для вывода содержимого структуры в файл EMPLOYEE.DAT:

#include <iostream > #include <fstream > using namespace std; void main()

{ struct employee { char name[64]; int age; float salary; } worker = { "Иванов Петя", 33, 25000.0 };

ofstream emp_file("EMPLOYEE.DAT") ; emp_file.write((char *) &worker, sizeof(employee)); }

Функция write обычно получает указатель на символьную строку. Символы (char *) представляют собой оператор приведения типов, который информирует компилятор, что вы передаете указатель на другой тип.

Строковые потоки

Поток может быть привязан к файлу, т.е. массиву символов, хранящемуся не в основной памяти, а, например, на диске. Точно так же поток можно привязать к массиву символов в основной памяти. Например, можно воспользоваться выходным строковым потоком ostrstream для форматирования сообщений, не подлежащих немедленной печати:

char* p = new char[message_size]; ostrstream ost(p,message_size); do_something(arguments,ost); display(p);

С помощью стандартных операций вывода функция do_something может писать в поток ost, передавать ost подчиняющимся ей функциям и т.п. Контроль переполнения не нужен, поскольку ost знает свой размер и при заполнении перейдет в состояние, определяемое fail(). Затем функция display может послать сообщение в "настоящий" выходной поток. Такой прием наиболее подходит в тех случаях, когда окончательная операция вывода

90

предназначена для записи на более сложное устройство, чем традиционное, ориентированное на последовательность строк, выводное устройство. Например, текст из ost может быть помещен в фиксированную область на экране. Аналогично, istrstream является вводным строковым потоком, читающим из последовательности символов, заканчивающейся нулем:

void word_per_line(char v[], int sz)

/* печатать "v" размером "sz" по одному слову в строке */

{istrstream ist(v,sz); // создать istream для v

char b2[MAX]; // длиннее самого длинного слова while (ist>>b2) cout <<b2 << "\n"; }

Завершающий нуль считается концом файла. Строковые потоки описаны в файле <strstream.h>. Вывод осуществляется, как правило, с помощью перегруженного оператора сдвига влево (<<), а ввод – с помощью оператора сдвига вправо (>>). Помимо чтения с терминала и записи на него, библиотека iostream поддерживает чтение и запись в файлы. Для этого предназначены следующие классы:

· ifstream, производный от istream, связывает ввод программы с файлом;

ofstream, производный от ostream, связывает вывод программы с файлом;

fstream, производный от iostream, связывает как ввод, так и вывод программы с файлом.

Пример. Программа работа с файлами #include <fstream>

#include <string> #include <vector> #include <algorithm> using namespace std; int main() {

string ifile;

cout << "Введите имя файла для сортировки: ";

cin >> ifile; // сконструировать объект класса ifstream для ввода из файла ifstream infile( ifile.c_str() );

if ( ! infile ) {cerr << "ошибка: открытия входного файла: "<< ifile << endl; return -1; }

string ofile = ifile + ".sort";

// сконструировать объект класса ofstream для вывода в файл ofstream outfile( ofile.c_str() );

if ( ! outfile) { cerr << "ошибка: открытия выходного файла: " << ofile << endl; return -2; }

string buffer;

91

vector< string, allocator > text; int cnt = 1;

while ( infile >> buffer ) { text.push_back( buffer ); cout << buffer << (cnt++ % 8 ? " " : "\n" ); }

sort( text.begin(), text.end() );// вывод отсортированного множества слов в файл vector< string >::iterator iter = text.begin();

for ( cnt = 1; iter != text.end(); ++iter, ++cnt ) outfile << *iter<< (cnt % 8 ? " " : "\n" );

return 0; }

Буферизация

Нельзя одинаково работать со всеми устройствами без учета алгоритма буферизации. Очевидно, что потоку ostream, привязанному к строке символов, нужен не такой буфер, как ostream, привязанному к файлу. Такие вопросы решаются созданием во время инициализации разных буферов для потоков разных типов. Но существует только один набор операций над этими типами буферов, поэтому в ostream нет функций, код которых учитывает различие буферов. Однако, функции, следящие за переполнением и обращением к пустому буферу, являются виртуальными. Это хороший пример применения виртуальных функций для единообразной работы с эквивалентными логически, но различно реализованными структурами, и они вполне справляются с требуемыми алгоритмами буферизации. Описание буфера потока в файле <iostream.h> может выглядеть следующим образом:

class streambuf { // управление буфером потока protected:

char* base;// начало буфера

char* pptr;// следующий свободный байт char* gptr;// следующий заполненный байт

char* eptr;// один из указателей на конец буфера char alloc;// буфер, размещенный с помощью "new" //...

//Опустошить буфер:

//Вернуть EOF при ошибке, 0 - удача virtual int overflow(int c = EOF);

//Заполнить буфер:

//Вернуть EOF в случае ошибки или конца входного потока,

//иначе вернуть очередной символ

virtual int underflow(); //...

public:

92

streambuf(); streambuf(char* p, int l); virtual ~streambuf();

int snextc() // получить очередной символ

{

return (++gptr==pptr) ? underflow() : *gptr&0377;

}

int allocate(); // отвести память под буфер //...

};

Подробности реализации класса streambuf приведены здесь только для полноты представления. Обратите внимание на определенные здесь указатели, управляющие буфером; с их помощью простые посимвольные операции с потоком можно определить максимально эффективно (и причем однократно) как функции-подстановки. Только функции overflow() и underflow() требует своей реализации для каждого алгоритма буферизации, например:

class filebuf : public streambuf {

protected:

 

int fd;

// дескриптор файла

char opened;

// признак открытия файла

public:

filebuf() { opened = 0; } filebuf(int nfd, char* p, int l) : streambuf(p,l) { /* ... */ } ~filebuf() { close(); }

int overflow(int c=EOF); int underflow();

filebuf* open(char *name, ios::open_mode om); int close() { /* ... */ }

//...

};

int filebuf::underflow() // заполнить буфер из "fd"

{if (!opened || allocate()==EOF) return EOF; int count = read(fd, base, eptr-base);

if (count < 1) return EOF; gptr = base; pptr = base + count;

return *gptr & 0377;}// &0377 предотвращает размножение знака

}

93

Лабораторная работа № 10 Тема: Потоки и файлы

Цель работы: Изучить создание потоковых операций ввода-вывода Задание.

1

1.Создайте приложение для контроля ввода данных английских расстояний футов и дюймов. Футы представлены типом int , дюймы представлены типом float. Для футов значение должно быть в пределах от -999 до 999. Число для дюймов должно быть от 0.0 до 12.0 Должно отслеживаться наличие ошибок ios. При вводе контролировать переизбыток символов с помощью функции ignore( ). В программе предоставьте входному потоку не игнорировать разделители с помощью флага skipws

2.Создайте приложение, позволяющее записывать символ, целое число, числа типа double, два объекта типа string в файл на диске с помощью объекта класса ofstream. Между данными поместите пробелы.

3.Создайте приложение, позволяющее считывать данные из файла,

созданного в упражнении 2. Используйте объект класса ifstream, инициализированный именем файла. Выведите данные при использовании различных параметров форматирования.

4. Создайте приложение, позволяющее записывать объекты в двоичном (бинарном) режиме. В этом приложении должны быть записаны данные о служащих (class employees): таб. номер, фамилия, имя, должность, подразделение. По запрашиваемому номеру считываются необходимые данные.

2

1.Создайте приложение, в котором читается файл вещественных чисел, из прочитанных чисел составляются пары комплексных чисел, которые затем

записываются в файл с комплексными числами. В приложении запрашивается номер комплексного числа и оно считывается из этого файла.

2.Определить тип name_and_address (тип_и_адрес). Определить для него << и >>. Написать программу копирования объектов потока name_and_address.

3.Разработать несколько функций для запроса и чтения данных разных типов. Предложения: целое, вещественное число, имя файла, почтовый адрес, дата, личная информация, и т.п. Попытайтесь сделать их устойчивыми к ошибкам.

4.Напишите программу, которая печатает: (1) строчные буквы, (2) все буквы,

(3) все буквы и цифры, (4) все символы, входящие в идентификатор в вашей версии С++, (5) все знаки пунктуации, (6) целые значения всех управляющих символов, (7) все обобщенные пробелы, (8) целые значения всех обобщенных пробелов, и, наконец, (9) все изображаемые символы.

94

5. Напишите программу, эмулирующую команду COPY (MS DOS). То есть программа должна копировать содержимое одного файла в другой. Должно использоваться два аргумента командной строки — исходный файл и файл назначения. Осуществляйте проверку числа аргументов командной строки и возможность открытия указанных пользователем файлов.

Задание для самостоятельной работы

1.Напишите программу, возвращающую размер файла, указанного в командной строке.

2.Напишите программу, в которой у пользователя запрашиваются данные, состоящие из номера водительских прав, фамилии, номера автомобиля, марки автомобиля. Затем осуществите форматированный вывод в объект ofstream с помощью оператора вставки (<<). Строки данных должны оканчиваться пробелами или другими разделителями. Когда пользователь сообщит об окончании ввода, закройте объект ofstream, прочитайте и выведите на экран все данные из файла, после чего завершите программу.

3.Создайте класс avto, включающий в себя данные из предыдущего упражнения. Создайте методы для этого класса, осуществляющие файловый ввод/вывод данных указанного класса (с использованием ofstream и ifstream). Используйте форматирование данных (операторы << и >> ). Функции чтения и записи должны быть независимыми: в них необходимо внести выражения для открытия соответствующего потока, а также чтения и записи данных.

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

95

Глава 11. Стандартная библиотека шаблонов

STL состоит из компонентов трех основных типов: контейнеров, алгоритмов и итераторов. Контейнеры содержат и организуют элементы. Алгоритмы осуществляют операции. Итераторы служат для доступа к элементам контейнера, с помощью шаблонов. Итератор имеет сходство с указателем. Как и указатель, итератор может ссылаться на элемент последовательности, может быть отслежен для получения значения объекта, на который он ссылается, и может выступать в качестве указателя для ссылки на разные элементы последовательности.

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

итераторы

могли

поддерживать

определенные

операции

и

идентифицировать назначение этих операций.

 

 

Кроме контейнеров, алгоритмов и итераторов, STL определяет ряд вспомогательных возможностей. Алгоритмы и контейнеры могут быть настроены с помощью указателей на функции и объектов-функций «Объекты-функции STL»), a эти объекты-функции могут адаптироваться и комбинироваться с помощью различных адаптеров объектовфункций.

Контейнеры подразделяются на два основных семейства: последовательные контейнеры и ассоциативные контейнеры. Последовательные контейнеры включают векторы (vectors), списки (lists) и двусторонние очереди (deques ). Эти контейнеры упорядочиваются заданием последовательности элементов. Ассоциативные контейнеры включают множества (sets ), мультимножества (multisets ), отображения (maps ), мультиотображения (multimaps ) и содержат ключи для поиска элементов. Контейнер отображения является ассоциативным массивом. Ему необходимо, чтобы была определена операция сравнения для хранимых элементов. Все варианты контейнеров имеют похожий интерфейс.

Интерфейсы типичных контейнеров STL

Контейнеры STL имеют конструкторы, включая конструкторы по умолчанию и копирующие конструкторы, деструктор; а также в них содержатся методы,

96

позволяющие осуществлять доступ к элементу; вставку элемента; удаление элемента; итераторы.

Проход по контейнеру осуществляется с помощью итераторов. В файле stl_deq.cpp

// типичный алгоритм контейнера

double sum ( const deque < double > & dq ) { deque < double > :: const_iterator p; double s = 0.0;

for (p =dq.begin (); p != dq. end ( ); ++p ) s += *p;

return s; }

Проход по контейнеру deque (double-ended queue - двусторонняя очередь) производится с помощью const_iterator. Итератор p разыменовывается для получения по очереди каждого хранимого элемента. Такой алгоритм будет работать для последовательных контейнеров и со всеми типами, для которых определен operator+= ( ).

В следующей таблице, описывающей интерфейс контейнерных классов, они обозначены как CAN.

Определение контейнеров STL

CAN ::value_type

что содержит CAN

 

CAN:: reference

тип-ссылка на значение

CAN :: const_reference

константная ссылка

CAN:: pointer

указатель на значение

CAN :: iterator

тип - итератор

 

CAN :: const_iterator

константный итератор

CAN :: reverse_iterator

обратный итератор

 

CAN :: const_reverse_iterator

константный

обратный

 

итератор

 

 

CAN :: difference_type

разница

между

двумя

 

значениями CAN::iterator

 

CAN::size_type

размер CAN

 

 

Эти определения доступны во всех контейнерных классах. Например, vector<char>::value_type покажет, что в векторном контейнере храниться символьное значение. Проход по такому контейнеру может быть выполнен с помощью vector <char>:: iterator.

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

Члены контейнера STL

97

CAN::CAN ( )

конструктор по умолчанию

CAN::CAN ( c )

копирующий конструктор

c.begin ( )

начальная позиция контейнера с

c.end ( )

конечная позиция контейнера с

c.rbegin ( )

начало для обратного итератора

c.rend ( )

конец для обратного итератора

c.size ( )

число элементов в CAN

c.max_size ( )

наибольший размер

c.empty ( )

истина, если CAN пуст

c.swap (d )

обмен элементами двух CAN

Примечание. Точнее, c.end ( ) указывает не на последний элемент контейнера. а на (несуществующий) элемент, “следующий за последним “. Такое соглашение С++ облегчает выполнение операций над контейнерами.

Операторы контейнера STL

= = != < > <= >=

операторы

равенства и

 

сравнения,

использующие

 

CAN::value_type

 

Последовательные контейнеры

Последовательные контейнеры – это вектор, список и двусторонняя очередь. Они содержат последовательность доступных элементов. В С++ тип массива во многих случаях может рассматриваться как последовательный контейнер.

В файле stl_vect.cpp

//последовательные контейнеры –вставка вектора в deque #include <cstdlib>

#include <iostream>

#include <deque>

#include <vector> using namespace std;

int main(int argc, char *argv[]) { int data [5] ={ 3,7,5, 11, 13};

vector<int> v(5, 17); //вектор из пяти элементов со значением 17 deque <int> d( data, data+5);

deque<int>:: iterator p;

cout <<"\nЗначения очереди "<<endl; for (p=d.begin ( ); p != d.end ( ) ; ++p ) cout << *p << '\t';

cout <<endl;

d.insert ( d.begin ( ), v.begin ( ), v.end ( ) );

98

for ( p=d.begin ( ); p!=d.end ( ); ++p ) cout <<*p <<'\t';

system("PAUSE"); return EXIT_SUCCESS; }

В этой программе вектор v, состоящий из 5 элементов, инициализируется значением 17. Двусторонняя очередь d инициализируется значениями, получаемыми из массива data. Функция-член insert () помещает

значения v в диапазоне от v.begin( ) до

v.end() с положения d.begin().

Итератор p объявлен, но не инициализирован.

for (p=d.begin ( ); p != d.end ( ) ; ++p )

cout << *p << '\t';

Выражение d.end() используется для выхода из цикла, поскольку является значением итератора “конец контейнера “. Автоинкремент ++ имеет семантику указателя, продвигая итератор к следующей позиции в контейнере. Разыименование работает аналогично семантике указателей.

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

Члены последовательных контейнеров STL

SEQ::SEQ (n, v )

n элементов со значением v

SEQ::SEQ (b_it, e_it)

от b_it до e_it-1

 

c.insert (w_it, v )

вставка v перед w_it

 

c.insert (w_it, v, n )

вставка n копий v перед w_it

c.insert (w_it, b_it, e_it )

вставка значений от b_it до

 

e_it перед w_it

 

c.erase (w_it )

стирает

элемент,

 

находящийся в w_it

 

c.erase (b_it, e_it )

стирает от b_it до e_it

Вот некоторые примеры использования этих методов: double w [7]= { 1.1, 1.2, 2.2, 2.3, 3.3, 3.4, 4.4, 4.5}; vector<double> v(11, 2.3); //11 элементов со значением 2.3

deque<double> d( w+2, w+6) ;//использует значения из массива от 2.2 до 4.4 d.erase (d.begin ()+2); // стирает 3-ий элемент

v. unsert (v. begin () =1, w[3]);//вставляет w[3]

Пример списочного контейнера, итератора и обобщенного алгоритма accumulate ()

//применение списочного контейнера #include <cstdlib>

#include <iostream>

#include <list>

#include <numeric> // необходимо для accumulate() using namespace std;

99

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]