Лабораторная работа №1

Тема: Потоки. Использование стандартного ввода-вывода (iostream). Использование стандартного ввода-вывода (fstream). Применение инструментов C++ Builder, Visual C++, Delphi, Borland J++, Visual J++.

Цель:

- познакомиться с потоками в С++;

- научиться создавать программы на С++ с использованием классов;

- уметь использовать стандартный ввод-вывод (iostream и fstream).

Краткая теория.

Потоки - одна из наименее понятных для новичков областей С++. В программах на языке С++ можно использовать стандартную библиотеку языка С, включая заголовочный файл 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().

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

Для вывода объекта в файл в определение класса может быть включена функция-компонента с параметром ссылкой на объект типа 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 с определениями необходимых классов и макросов.

Практические задания:

Использование стандартного ввода-вывода (iostream)

1. Написать программу вычисления ближайшего сверху числа степени 2; Программа должна использовать цикл for. Входные данные поступают с клавиатуры. Результат выводится на экран. Предусмотреть обработку ошибок.

2. Написать программу, печатающую цифры. Использовать цикл for. В строку выводится цифра, в десятичной, шестнадцатеричной и восьмеричной системе.

Использование стандартного ввода-вывода (fstream)

1. Написать программу копирования файла с удалением пробелов. Чтение происходит блоками. Обработать ошибки.

2. Написать программу вычисления количества символов перевода строки в файле. Чтение происходит блоками. Обработать ошибки.