##

## 6 Классы для ввода-вывода потоков

###

### 6.1 Система классов ввода-вывода

###

Потоки - одна из наименее понятных для новичков областей С++. В программах на языке С++ можно использовать стандартную библиотеку языка С, включая заголовочный файл stdio.h, а также библиотеку потоков, построенную на основе классов, включая заголовочный файл iostream.h. При хорошем стиле программирования желательно в программе на С++ использовать библиотеку классов iostream.

Поток определяется как последовательность байтов и с точки зрения программиста не зависит от устройств, с которыми ведется обмен данными. Обычно с потоком связана область памяти, называемая буфером. В буфер помещаются данные перед выводом на устройство или при вводе с устройства. Использование буфера повышает скорость обмена данными. В С++ потоки реализованы как иерархия классов потоков, поэтому при изучении потоков желательно хорошо представлять себе как организована эта иерархия, какие классы являются базовыми и как наследуются основные возможности классов. Рекомендуется при изучении потоков С++ изобразить иерархию классов графически, замечая, какие методы и данные наследуются из базовых классов. Особенно интересен с этой точки зрения базовый класс всей иерархии - ios.

Потоки обеспечивают:

· буферизацию данных;

· контроль типов данных;

· контроль за состоянием потоков;

· форматирование текста;

· обмен для типов пользователя - классов;

· независимость программы от файловой системы конкретной ЭВМ.

Все перечисленные возможности заложены в базовых классах, в основном, в ios.

Все потоки делятся на:

· стандартные, обеспечивающие обмен с клавиатурой и дисплеем (библиотека iostream.h);

· файловые, обеспечивающие работу с файлами (библиотека fstream.h);

· для работы с памятью, обеспечивающие обмен с памятью (библиотека strstrea.h);

· консольные, обеспечивающие удобный способ взаимодействия с терминалом (библиотека constrea.h).

Для каждой программы при включении в нее заголовочного файла iostream.h происходит формирование объектов cin, cout, cerr, clog. Эти объекты относятся к стандартным потокам. Для стандартных потоков определены операции вставки в поток << и извлечения из потока >> для всех встроенных типов данных. Эти же операции легко перегружаются для любых типов данных, т.е. классов пользователя.

Например,

class Point {
double x,y;
public:
Point (double ix, double iy):x(ix),y(iy){};
friend ostream& operator << ( ostream& os, Point& p );
friend istream& operator >> ( istream& os, Point& p );
};
ostream& operator << ( ostream& os, Point& p ) {

return os << '(' << p.x << ',' << p.y <<')'; }
istream& operator >> ( istream& os, Point& p ) { return is >> p.x >> p.y; }
void main() {
Point p(1.5,2.5);
cout << " Моя точка:" << p << endl;
cout << "Введите новую точку:";
cin >> p;
cout << " Новая точка:" << p << endl;
}

При вводе-выводе часто приходится применять форматирование. В классе ios определены флаги форматирования, позволяющие управлять выравниванием при выводе, шириной поля, задавать основание системы счисления и т.д. Флаги объединяются в компонентах ios - x_width, x_precision, x_fill, x_flags. Для управления флагами используется целый набор функций класса ios.

Использование этих компонентных функций не всегда удобно. Альтернативным способом является использование манипуляторов - специальных функций, позволяющих управлять флагами. Манипуляторы можно вставлять в цепочки ввода-вывода, используя операции << или >>. Различают манипуляторы с параметрами и без параметров. Особый интерес представляют манипуляторы, разработанные пользователем.

Кроме операций << и >>, в классах потоков есть весьма полезные функции для обмена. Это функции потоков ostream - put(), write(),seekp(), tellp(), а также функции потоков istream - get(), getline(), putback(), peek(), ignore(), read(), seekg(), tellg(), gcount().

При работе с потоками часто приходится проверять их на наличие ошибок. Биты ошибок устанавливаются в переменной класса ios state.

enum io_state {

goodbit = 0x00, // no bit set: all is ok

eofbit = 0x01, // at end of file

failbit = 0x02, // last I/O operation failed

badbit = 0x04, // invalid operation attempted

hardfail = 0x80 // unrecoverable error

};

Для работы с битами ошибок используются функции класса ios:

int rdstate(); // return the stream state

int eof(); // non-zero on end of file

int fail(); // non-zero if an operation failed

int bad(); // non-zero if error occurred

int good(); // non-zero if no state bits set

void clear(int = 0); // set the stream state

Особый интерес для проверки потоков на ошибки представляют переопределенные операторы:

operator void * (); // zero if state failed

int operator! (); // non-zero if state failed

Потоки для работы с файлами создаются как объекты классов ofstream - для вывода в файл, ifstream - для ввода из файла, fstream - для чтения и записи данных. Эти классы описаны в заголовочном файле fstream.h. Поскольку эти классы являются производными от классов ios, ostream и istream, все методы, упомянутые выше, доступны. Основное отличие при работе с файлами заключается в том, что при создании файлового потока его надо присоединить к конкретному файлу. Это делается либо с помощью конструкторов, либо с помощью методов open():

void open( const char * filename, int mode = по_умолчанию,
int protection = по_умолчанию);

mode - параметр, определяющий режим работы с файлом (например, запись, чтение или и то и другое ). Флаги для задания этого режима определяются в классе ios в виде enum open_mode:

enum open_mode {

in = 0x01, // open for reading

out = 0x02, // open for writing

ate = 0x04, // seek to eof upon original open

app = 0x08, // append mode: all additions at eof

trunc = 0x10, // truncate file if already exists

nocreate = 0x20, // open fails if file doesn't exist

noreplace= 0x40, // open fails if file already exists

binary = 0x80 // binary (not text) file

};

Параметр protection зависит от операционной системы и, как правило, использует значение по умолчанию.

Ниже приведены примеры для работы с текстовыми и бинарными файлами.

// копирование текстовых файлов

void copytxt(char * input_name, char * output_name) {

ifstream input(input_name);
ofstream output(output_name);
char buf[256];
while ( input && output ) {
input.getline(buf,sizeof ( buf ));
output << buf << endl;
}
}
// копирование бинарных файлов
void copybin(char * input_name, char * output_name) {
ifstream input(input_name, ios::binary);
ofstream output(output_name, ios::binary);
char buf[512];
while ( input && output ) {
input.read(buf,sizeof ( buf ));
output.write(buf,input.gcount());
}
}

Часто приходится форматировать данные в памяти для последующей обработки. Для такой обработки предназначены классы istrstream, ostrstream, strstream, описанные в заголовочном файле strstream.h. Так как эти классы производны от ios, istream и ostream, то все методы применимы и к объектам этих классов. Ниже приводится пример использования этих потоков.

void writestr( char * name ){
ostrstream outstr;
outstr << "This message for message box" << endl;
outstr << "The name is " << name << endl;
cout << outstr.rdbuf();
}
void example1(){
char buf[128];
ostrstream text(buf,sizeof (buf));
text << "This is the example, i is equal " << i << endl;
cout << buf;
text.seekp(0);
text << " The second using of text\n";
cout << buf;
}
void example2(char * argv[]) {
char name;
istrstream input(argv[0]);
input >> name;
cout << " The first argument is " << name << endl;
}

Вопросы для самопроверки.

1. Можно ли перегрузить операторы >> и << как функции-члены класса? При любом ответе объясните, почему.

2. Разработайте класс для работы с комплексными числами. Предусмотрите перегрузку операторов >> и <<. Для ввода предусмотреть возможность задания комплексных чисел в виде (re,im), или re, или (re).

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

4. В чем разница между функциями get(char * array, int length, char = '\n') и getline(char * array, int length, char = '\n')? Поясните на примере.

5. Постройте манипулятор без параметров, включение которого в цепочку вывода приводит к выводу заголовка " Мой заголовок\n".

6. Может ли манипулятор быть членом класса? Приведите пример.

7. Поясните запись:

ifstream input(input_name, ios::binary);
if ( input ) {
// ошибок не было
};
if ( !input ) {
// ошибки были
};

8. В чем разница между функциями getline() и read()?

9. Как, используя только методы потоков, определить, существует ли данный файл? Привести пример.

10. Верна ли запись для копирования файлов:
ifstream input(input_name);
ofstream output(output_name);
output << input.rdbuf(); ?

11. Верны ли конструкции:

output << input.rdbuf();
input >> output.rdbuf(); ?

12. Почему в приведенных примерах явно не вызывается метод close() при работе с файлами? Когда необходимо вызывать этот метод?

13. Поясните метод rdbuf(). Какому классу он принадлежит?

Объясните запись, приведенную ниже.

char arr = " 000 111 222 333";
char array[80];
istrstream ( arr) >> array;
cout << array;

Система ввода-вывода С++ основывается на концепции потоков данных, поток представляет собой, с одной стороны, последовательность данных, с другой стороны поток рассматривается как переменная некоторого объектного типа. Это позволяет вынести общие свойства и операции процессов ввода-вывода в определения базовых классов. Ввод-вывод из файлов и консоли, как правило, выполняется с использованием буфера и получение данных программой или вывод данных сводится к пересылке данных из одной области памяти в другую. Реальное обращение к внешним устройствам происходит только при исчерпании данных в буфере (при вводе) или при заполнении буфера (при выводе).

Система классов ввода-вывода С++ использует два базовых класса: класс ios и класс streambuf.

В классе ios определены данные, характеризующие состояние потока, и функции, позволяющие получить доступ к информации о состоянии потока или изменить его состояние. Состояние потока определяется набором битовых флагов, для обращения к отдельным флагам в классе ios описаны перечислимые константы:

- биты состояния (статуса) потока

    enum io_state { goodbit = 0x00, // никакие биты не установлены, все хорошо

    eofbit = 0x01, // конец файла

    failbit = 0x02, // ошибка в последней операции ввода/вывода

    badbit = 0x04, // попытка выполнить неверную операцию

    hardfail = 0x80 // неисправимая ошибка

    };

- биты режима использования потока (режима ввода/вывода)

    enum open_mode { in = 0x01, // поток открыт для чтения

    out = 0x02, // поток открыт для записи

    ate = 0x04, // перейти в конец файла при открытии

    app = 0x08, // режим добавления в конец файла

    trunc = 0x10, // усечение существующего файла

    nocreate = 0x20, // ошибка открытия файла, если он не существует

    noreplace = 0x40, // ошибка открытия, если файл существует

    binary = 0x80 // двоичный (не текстовый) файл

    };

- флаги направления позиционирования в потоке

    enum seek_dir { beg=0, cur=1, end=2 };

- флаги - манипуляторы управления вводом/выводом

    enum { skipws = 0x0001, // пропускать пробелы при вводе

    left = 0x0002, // выравнивание влево при выводе

    right = 0x0004, // выравнивание вправо при выводе

    internal = 0x0008, // пробел после знака или основания системы счисления

    dec = 0x0010, // преобразование в десятичную систему счисления

    oct = 0x0020, // преобразование в восьмеричную систему счисления

    hex = 0x0040, // шестнадцатеричное преобразование

    showbase = 0x0080, //использовать индикатор системы счисления при //выводе

    showpoint = 0x0100, // указывать десятичную точку при выводе

                                    //(в числах с плавающей точкой)

    uppercase = 0x0200, // прописные буквы при шестнадцатеричном выводе

    showpos = 0x0400, // добавлять '+' для положительных целых

    scientific= 0x0800, // применять нотацию вида 1.2345E2

    fixed = 0x1000, // применять нотацию вида 123.45

    unitbuf = 0x2000, // очищать все потоки после вставки в поток

    stdio = 0x4000, // очищать stdout, stderr после вставки в поток

    boolalpha = 0x8000  // вставлять/извлекать булевы как текст или цифры

    };

Поскольку эти перечислимые константы объявлены как компоненты класса ios, для доступа к ним требуется уточнение контекста, например, ios::in.

Класс streambuf обеспечивает создание и использование буфера ввода-вывода и содержит компоненты-данные для управления буфером и методы доступа к данным в буфере. Объект класса ios содержит указатель на связанный с ним объект streambuf.

Классы istream и ostream являются производными от класса ios, в них определены функции, выполняющие ввод (istream) и вывод (ostream) данных базовых типов и строк.

В классе istream определены операции бесформатного ввода (без преобразования вводимых данных)и операции форматного ввода с преобразованием из внешнего представления во внутреннее.

Функции get() читают из потока данные типа char в массив, определяемый первым параметром, второй параметр задает максимальное число вводимых символов (l), третий параметр устанавливает символ-ограничитель, ограничивающий ввод. За последним введенным символом в массив пишется символ ‘\0’.

    istream _FAR & _RTLENTRY get( char _FAR *, int l, char = '\n');

    istream _FAR & _RTLENTRY get( signed char _FAR *, int l, char = '\n');

    istream _FAR & _RTLENTRY get( unsigned char _FAR *, int l, char ='\n');

Функции read() выполняют чтение из потока и занесение в массив указанного числа символов

    istream _FAR & _RTLENTRY read( char _FAR *, int l);

    istream _FAR & _RTLENTRY read( signed char _FAR *, int l);

    istream _FAR & _RTLENTRY read(unsigned char _FAR *, int l);

Для ввода строки, заканчивающейся символом-ограничителем, служит функция getline() при этом символ-ограничитель также заносится в массив

    istream _FAR & _RTLENTRY getline( char _FAR *, int, char = '\n');

    istream _FAR & _RTLENTRY getline( signed char _FAR *, int, char= '\n');

    istream _FAR & _RTLENTRY getline(unsigned char _FAR *, int, char= '\n');

Для извлечения из потока данных типа char вплоть до символа-ограничителя служит вариант функции get():

    istream _FAR & _RTLENTRY get(streambuf _FAR &, char = '\n');

Другие варианты функции get() обеспечивают извлечение одного символа

    istream _FAR & _RTLENTRY get( char _FAR &);

    istream _FAR & _RTLENTRY get( signed char _FAR &);

    istream _FAR & _RTLENTRY get(unsigned char _FAR &);

    int _RTLENTRY get(); // возвращает значение символа

    int _RTLENTRY peek(); // возвращает следующий символ без удаления его из потока

    int _RTLENTRY gcount(); // число символов, извлеченных из потока

    // возвращение указанного символа в поток ввода

    istream _FAR & _RTLENTRY putback(char);

Пропуск символов с остановкой по ограничителю выполняет функция

    istream _FAR & _RTLENTRY ignore(int = 1, int = EOF);

Форматный ввод релизуется на основе переопределения операций ввода

    istream _FAR & _RTLENTRY operator>> (bool _FAR &);

    istream _FAR & _RTLENTRY operator>> (istream _FAR & (_RTLENTRY *_f)(istream _FAR &));

    istream _FAR & _RTLENTRY operator>> (ios _FAR & (_RTLENTRY *_f)(ios _FAR &) );

    istream _FAR & _RTLENTRY operator>> ( char _FAR *);

    istream _FAR & _RTLENTRY operator>> ( signed char _FAR *);

    istream _FAR & _RTLENTRY operator>> (unsigned char _FAR *);

    istream _FAR & _RTLENTRY operator>> ( char _FAR &);

    istream _FAR & _RTLENTRY operator>> ( signed char _FAR &);

    istream _FAR & _RTLENTRY operator>> (unsigned char _FAR &);

    istream _FAR & _RTLENTRY operator>> (short _FAR &);

    istream _FAR & _RTLENTRY operator>> (int _FAR &);

    istream _FAR & _RTLENTRY operator>> (long _FAR &);

    istream _FAR & _RTLENTRY operator>> (unsigned short _FAR &);

    istream _FAR & _RTLENTRY operator>> (unsigned int _FAR &);

    istream _FAR & _RTLENTRY operator>> (unsigned long _FAR &);

    istream _FAR & _RTLENTRY operator>> (float _FAR &);

    istream _FAR & _RTLENTRY operator>> (double _FAR &);

    istream _FAR & _RTLENTRY operator>> (long double _FAR &);

Извлечение из istream и вставка в объект типа streambuf выполняет оператор-функция

    istream _FAR & _RTLENTRY operator>> (streambuf _FAR *);

Макрос FAR управляет способом представления указателей, макрос RTLENTRY определяет применение стандартной библиотеки времени выполнения.

Аналогичные операции для вывода определены в классе ostream:

    // вставить символ в поток

    ostream _FAR & _RTLENTRY put( char);

    ostream _FAR & _RTLENTRY put(signed char);

    ostream _FAR & _RTLENTRY put(unsigned char);

    // вставить в поток строку

    ostream _FAR & _RTLENTRY write(const char _FAR *, int l);

    ostream _FAR & _RTLENTRY write(const signed char _FAR *, int l);

    ostream _FAR & _RTLENTRY write(const unsigned char _FAR *, int l);

Операции форматированного вывода

Вывод "true" или "false" для данных типа bool

    ostream _FAR & _RTLENTRY ostream::operator<< (bool);

Вывод данных типа char

    ostream _FAR & _RTLENTRY operator<< ( char);

    ostream _FAR & _RTLENTRY operator<< ( signed char);

    ostream _FAR & _RTLENTRY operator<< (unsigned char);

Вывод числовых данных с преобразованием во внешнее представление

    ostream _FAR & _RTLENTRY operator<< (short);

    ostream _FAR & _RTLENTRY operator<< (unsigned short);

    ostream _FAR & _RTLENTRY operator<< (int);

    ostream _FAR & _RTLENTRY operator<< (unsigned int);

    ostream _FAR & _RTLENTRY operator<< (long);

    ostream _FAR & _RTLENTRY operator<< (unsigned long);

    ostream _FAR & _RTLENTRY operator<< (float);

    ostream _FAR & _RTLENTRY operator<< (double);

    ostream _FAR & _RTLENTRY operator<< (long double);

Вывод строк, оканчивающихся нулевым байтом

    ostream _FAR & _RTLENTRY operator<< (const char _FAR *);

    ostream _FAR & _RTLENTRY operator<< (const signed char _FAR*);

    ostream _FAR & _RTLENTRY operator<< (const unsigned char _FAR*);

Вывод значения указателя в символьном формате

    ostream _FAR & _RTLENTRY operator<< (void _FAR *);

Извлечение данных их объекта streambuf и вставка в тот же ostream

    ostream _FAR & _RTLENTRY operator<< (streambuf _FAR *);

Вывод значений манипуляторов

    ostream _FAR & _RTLENTRY operator<<(ostream _FAR & (_RTLENTRY *_f)(ostream _FAR &));

    ostream _FAR & _RTLENTRY operator<<(ios _FAR & (_RTLENTRY *_f)(ios _FAR &));

Имеется также класс iostream, производный от класса ios и объединяющий возможности классов istream и ostream.

Для рассмотренных выше классов отсутствуют конструкторы копирования и операция присваивания, точнее, они объявлены, но не определены. Для тех случаев, когда конструктор копирования и операция присваивания необходимы, предусмотрены классы istream_withassign, ostream_withassign и iostream_withassign. Как экземпляры объектов этих классов всегда объявляется объект cin (экземпляр istream_withassign), обычно предназначенный для ввода с клавиатуры, и объекты cout, cerr и clog (экземпляры ostream_withassign), обычно предназначенные для вывода на экран.

Ввод-вывод для дисковых файлов обепечивается классами, описания которых содержатся в файле fstream.h.

Класс filebuf, производный от streambuf, предназначен для добавления в streambuf дополнительных средств управления буфером ввода-вывода.

Класс fstreambase, производный от класса ios, служит базой для остальных классов, обеспечивающих файловый ввод-вывод, в нем определены методы:

    void _RTLENTRY open(const char _FAR *, int, int = filebuf::openprot);

    void _RTLENTRY attach(int);

    void _RTLENTRY close();

    void _RTLENTRY setbuf(char _FAR *, int);

Назначение этих методов очевидным образом следует из их названий.

Для непосредственной работы с файлами служат классы ifstream, ofstream и fstream, базой для них служат классы fstreambase и, соответственно, istream, ostream и iostream.

###

###

###

###

### 6.2 Вывод в файл. Ввод из файла

###

Для вывода данных в дисковый файл в программе должна присутствовать директива препроцессора

    #include <fstream.h>

###### подключающая описания необходимых классов.

Прежде чем выводить данные необходимо создать объект типа ofstream, для которого имеется несколько конструкторов:

    _RTLENTRY ofstream(); // Пустой объект, без привязки к файлу

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

    _RTLENTRY ofstream(const char _FAR *, int = ios::out, int = filebuf::openprot);

    // С привязкой к ранее открытому файлу, заданному своим дескриптором

    _RTLENTRY ofstream(int);

    // То же, что и предыдущий вариант, но задается новый буфер вывода

    _RTLENTRY ofstream(int __f, char _FAR *, int);

Наиболее часто оказывается полезным второй вариант конструктора, в котором указывается только первый параметр - полное имя файла. Этот конструктор создает объект типа ofstream, открывает указанный файл и присоединяет его к потоку вывода.

Собственно операции вывода реализуются вызовом методов put, write или с использованием переопределенных операций <<.

Аналогичным способом обеспечивается и ввод из файла: создается объект типа ifstream и для ввода применяются методы get, read или переопределенные операции >>.

Для типа ifstream имеется набор аналогичных конструкторов:

    _RTLENTRY ifstream();

    _RTLENTRY ifstream(const char _FAR *,int = ios::in, int = filebuf::openprot);

    _RTLENTRY ifstream(int);

    _RTLENTRY ifstream(int __f, char _FAR *, int);

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

    #include <fstream.h>

    #include <process.h> // Для вызова exit

    int main ( int argc, char* argv [ ] )

    { char ch;

    if ( argc != 3 ) // Проверка числа аргументов

    { cerr << “ Вызов dcopy файл1 файл2 \n” ; exit ( 1 ); }

    ifstream source( argv [ 1 ] ); // Входной поток

    if ( ! source )

    { cerr << “ Нельзя открыть входной файл “ << argv [ 1 ];

    exit ( 1 ); }

    ofstream dest ( argv [2 ] );

    if ( ! dest )

    { cerr << “ Нельзя открыть выходной файл “ << argv [ 2 ];

    exit ( 1 ); }

    while ((ch = source.get ( ) ) != EOF )

    dest.put( ch );

    close ( source ); close ( dest );

    return 0;

    }

###

### 6.3 Ввод-вывод данных объектных типов

###

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

Для вывода объекта в файл в определение класса может быть включена функция-компонента с параметром ссылкой на объект типа ostream. Часто такой функции назначают имя print или printon. Более изящным считается переопределение оператора << для вывода объектного данного. Если компоненты-данные объекта имеют уровень доступа protected или private, а оператор << не является компонентой класса, его следует объявить как friend-метод.

Пусть, например, в программе определен класс complex:

    class complex

    { double re, im ;

    public:

    complex (double r =0, double i =0 ): re ( r ), im ( i )

    { } // конструктор

    double real ( ) {return ( re ); }

    double image ( ) { return ( im ); }

    /* другие методы */

    }

Тогда для форматированного вывода комплексного числа в формате (вещественная часть, мнимая часть) можно так переопределить операцию << :

    ostream& operator << ( ostream& s, complex c )

    { int old_precision = s.precision ( 4 ); // установка числа дробных цифр

    s << “(“ << c.real( ) << “, “ << c.image( ) << “)” ;

    s.precision ( old_precision ) ; // восстановление числа дробных цифр

    return (s);

    }

В данном случае в переопределении << нет обращения к личным переменным класса Complex. Если не использовать методы real и image, переопределение << нужно было бы включить в описание класса Complex с описателем friend.

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

Пусть в описание класса Complex включена переопределенная операция >>:

    class complex

    { double re, im ;

    public:

    complex (double r =0,double i=0 ): re( r ), im( i ) { } // конструктор

    double real ( ) {return ( re ); }

    double image ( ) { return ( im ); }

    friend istream& operator >> ( istream& , Complex& );

    /* другие методы */

    }

Если предположить, что комплексные числа поступают из потока либо в виде вещественного числа, возможно заключенного в скобки, либо в виде пары чисел, разделенных запятой и заключенной в скобки, то переопределяемую операцию ввода можно описать следующим образом:

    istream& operator >> ( istream& s, Complex& c )

    { double re_n = 0, im_n = 0;

    char ch = 0;

    s >> ch ;

    if ( ch == ‘(‘ )

    { s >> re_n >> ch ;

    if ( ch == ‘,’ ) s >> im_n >> ch;

    if ( ch != ‘)’) s.clear ( ios::failbit ); //Установка признака ошибки

    }

    else { s.putback ( ch ); s >> re_n ; }

    if ( s ) { c.re = re_n; c.im = im_n; } // Если не было ошибки

    return ( s );

}

Рассмотренные выше примеры сохранения объектов в потоке и восстановления их из потока иллюстрируют наиболее простые варианты этих операций. Проблема сохранения объектов в потоке существенно усложняется, когда требуется сохранять в одном потоке объекты разных типов, когда между объектами разных типов имеются ссылки и некоторый объект содержит указатель на объект другого типа, причем на один и тот же объект могут ссылаться несколько других объектов. В этой ситуации требуется для каждого сохраняемого в потоке объекта заносить в поток идентификатор типа объекта, гарантировать, что каждый объект, на который имеются ссылки из других объектов, будет помещен в поток только один раз. Средства для разрешения этих проблем имеются в библиотеке классов-контейнеров classlib, содержащей файл objstrm.h с определениями необходимых классов и макросов.

6.4 Выбор инструментальных средств программирования

6.4.1 Классы и методы в языке Java

Трудно сказать, возможно язык Java (не путать с технологией Java) можно описать как диалект языка С++. В них много общего, но и много отличий. Язык Java не имеет указателей, ссылок (точнее там все ссылки), структур оператора goto, функций (только методы) и перегрузки операторов. Он поддерживает строки как примитивный тип данных и выполняет сборку мусора.

Сам Java язык общего назначения, но основное его использование в области Internet и сетевых распределенных приложений. Мы не затрагиваем эти аспекты совсем и только касаемся языка.

Рассмотрим класс на Java
class card {
// статические значения цветов и мастей
final public int red =0;
final public int black =0;
final public int spade =0;
final public int heart =1;
final public int diamond =2;
final public int club =3;
// поля данных
private boolean faceup;
private int r;
private int s;
// конструктор
card(int sv,int rv) {
s=sv; r=rv; faceup=false; }
// доступ к атрибутам карты
public int rank() { return r; }
public int suit() { return s; }
public int color () {
if( suit()==heart || suit()==diamond)
return red;
else
return black; }
public boolean faceUp() { return faceup; }
public void draw (Graphics g, int x, int y) {
/* */
}
public void flip() {
faceup != faceup;
}
};
Описание класса на языке Java очень похоже, за исключением нескольких моментов

· Отсутствие препроцессора, глобальные переменные, перечисляемые типы данных. Символьные константы могут быть созданы и инициализированы с помощью ключевого слова final. Такие значения не могут изменяться.

· Реализация методов проводится непосредственно внутри определения класса. Вместо разбиения описания класса на public и private эти ключевые слова присоединяются в явном виде к каждой переменной или методу.

· Логический тип данных, именуемый boolean вместо bool.

· За исключением конструкторов все методы должны иметь возвращаемое значение

6.4.2 Ключевое слово this

Нестатические функции-члены класса оперируют с тем объектом типа класса, из которого они вызваны. Ключевое слово this обозначает специальную локальную переменную, доступную в теле любой функции-члена класса, описанной без спецификации static. Переменная this не требует описания и всегда содержит указатель соответствующего объекта.

· this->имя члена указывает на объект, членом, которого он является.

· *this представляет собой сам объект и, в зависимости от контекста, может быть лево- или правосторонней величиной

· this представляет собой адрес объекта.

Пример
class C {
int c1,c2;
public:
void init(int b) { c2=b; c1=b+1; }
C & inc() { c1++; c2++; return *this; }
void *adress() { return this; }
void print() { cout << c1 << c2; }
};
void main(void) {
C a;
a.init(10); a.print(); cout << “ adress=“ << a.adress<< “ inc “ << a.inc().print() << endl;
}

Указатель на объект, для которого вызвана функция-член, является скрытым параметром функции. На этот неявный параметр можно ссылаться явно как на this. В каждой функции класса x указатель this неявно описан как x* this; и инициализирован так, что он указывает на объект, для которого была вызвана функция-член. this не может быть описан явно, так как это ключевое слово. Класс x можно эквивалентным образом описать так:

class x {
int m;
public:
int readm() { return this->m; }
};

class x {
int m;
public:
int readm() { return m;