Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Курс ПЯВУ 2 сем / Лекции 2 сем / Л№34.Потоковый ввод / Лекция № 32.Потоковый ввод вывод.odt
Скачиваний:
11
Добавлен:
17.04.2015
Размер:
153.09 Кб
Скачать

Void main()

{

point F = { 10.0, 20.0, 30.0 };

cout << "\nКоординаты точки: " << F;

}

то на экране дисплея получим:

Координаты точки:

х = 10.0 у = 20.0 z = 30.0

Как видите, в данном примере при выводе в поток cout равноправно используются и вывод значения базового типа char [], и вывод значений объекта F типа point, определенного пользователем.

Напомним, что класс ostream, поток (объект) cout и "стандартные" режимы выполнения операции вывода (для базовых типов) определены в заголовочном файле iostream.h, который необходимо поместить в начале текста программы до текста операции-функции operator << ().

В качестве еще одного примера перегрузки (расширения действия) операции вывода <<, рассмотрим следующую программу, в которой для представления почтового адреса используется структурный тип с названием address:

//OOР8_1.СРР - перегрузка операции вывода "<<".

#include <iostream.h>

struct address // Почтовый адрес.

{

char *country; // Страна.

char *city; // Город.

char *street; // Улица.

int number_of_house; // Номер дома.

};

// Определение операции-функции, "распространяющей"

// действие операции включения в поток << на операнд

// типа address:

ostream& // Тип возвращаемого значения.

operator << (ostream& out, address ad)

{

out << "\nCountry: " << ad.country;

out << "\nCity: " << ad.city;

out << "\nStreet: " << ad.street;

out << "\nHouse: " << ad.number_of_house;

return out;

}

void main()

{

address ad = { "Russia", "Kurgan", "Gorky", 27 };

cout <<"\nЗначение структуры (почтовый адрес):";

cout << ad << "\n";

}

Текст этой программы можно взять здесь.

Результаты выполнения программы:

Значение структуры (почтовый адрес):

Country: Russia

City: Kurgan

Street: Gorky

House: 27

В теле операции-функции operator << () использовано несколько операторов с выражениями, выводящими значения в поток вывода. В операторе return возвращается ссылка на него (имеющая тип ostream&).

На следующем шаге мы закончим разбирать перегрузку операции вывода.

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

Чтобы решить указанную задачу, необходимо определить структуру, компонентами которой будут связанный со строкой указатель char * и целая переменная со значением, равным длине строки. Вот для такой структуры перегрузка операции вывода << вполне допустима. В следующей программе это реализовано:

//OOР9_1.СРР - вывод информации о структуре-строке.

#include <iostream.h>

#include <string.h> // Для работы со строковыми функциями.

//Определение класса (пользовательского типа):

struct string

{

int length;

char line[80];

};

// Прототип операции-функции для перегрузки операции <<:

ostream& operator << (ostream& out, string str);

void main()

{

string st; // Объект st класса string.

strcpy(st.line,"Содержимое строки.");

st.length = strlen(st.line);

cout << st;

}

ostream& // Тип возвращаемого значения.

operator << (ostream& out, string str)

{

out << "\n Длина строки: " << str.length;

out << "\n Значение строки: " << str.line;

return out;

}

Текст этой программы можно взять здесь.

Результат выполнения программы:

Длина строки: 18

Значение строки: Содержимое строки.

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

На следующем шаге мы рассмотрим перегрузку операции ввода.

Для перегрузки (расширения действия) операции ввода >> необходимо определить операцию-функцию вида:

istream& operator >> (istream& in, новый_тип& имя)

{ // Любые операторы для параметра нового типа.

in >> ... // Ввод значений нового типа.

return in; // Возврат ссылки на объект класса istream.

}

Здесь новый_тип - тип, определенный пользователем, т.е. некоторый класс или его частный случай - структурный тип. Основное отличие от перегрузки операции вывода - необходимость в качестве второго параметра использовать ссылку. Для уже введенного на предыдущих шагах структурного типа "точка трехмерного евклидова пространства" можно с помощью перегрузки операции ввода >> очень изящно определить последовательность ввода с консоли значений координат. Например, удобной может оказаться операция-функция, использованная в следующей программе:

//OOР10_1.СРР - перегрузка операции ввода >>.

#include <iostream.h>

struct point // Точка трехмерного евклидова пространства.

{

float x;

float y;

float z;

};

istream& // Тип возвращаемого значения.

operator >> (istream& in, point& d)

{

cout <<"\n Введите три координаты точки: " << "\nx = ";

in >> d.x;

cout << "y = "; in >> d.y;

cout << "z = "; in >> d.z;

return in;

}

void main()

{

point D;

cin >> D;

}

Текст этой программы можно взять здесь.

При выполнении на экране может быть, например, такой текст:

Введите три координаты точки:

х = 100 <Enter>

у = 200 <Enter>

z = 300 <Enter>

В предложенной операции-функции operator >> () выполняет не только "чтение" (ввод) данных, набираемых пользователем на клавиатуре (в примере 100, 200, 300), но и выводит на экран соответствующие подсказки, что упрощает использование программы. В данной программе операция-функция operator >> () работает не только со входным потоком, ссылка на который передается как параметр, но и со стандартным выходным потоком cout, для которого обычным образом используется операция вывода <<. Поэтому до определения операции-функции operator >> () в текст программы необходимо включить заголовочный файл iostream.h.

Следующая программа еще раз иллюстрирует сказанное. В программе определен структурный тип employee, предназначенный для структур, содержащих сведения о некоторых "служащих":

фамилия - char name [50];

оклад - long salary;

возраст - int age;

В теле операции-функции operator >> (), распространяющей действие операции ввода >> на структуру типа employee, на экран (поток cout) выводятся подсказки и из потока типа istream считываются набираемые на клавиатуре данные.

Текст программы:

//OOР10_2.СРР - перегрузка операций ввода >> и вывода <<.

#include <iostream.h>

// Определение класса "служащий":

struct employee

{

char name[50]; // Фамилия.

long salery; // Оклад.

int age; // Возраст.

};

// Определение операции-функции, "распространяющей"

// действие операции ввода " на операнд типа employee:

istream& // Тип возвращаемого значения.

operator >> (istream& input, employee& em)

{

cout << "\n\nВведите сведения о служащем:" <<

"\nФамилия: ";

input >> em.name;

cout << "Оклад: ";

input >> em.salery;

cout << "Возраст: ";

input >> em.age;

return input;

}

//Прототип операции-функции для перегрузки операции <<:

ostream& operator << (ostream&, employee);

void main ()

{

employee E; // Определен объект класса employee.

cin >> E;

cout << E;

}

// Определение операции-функции для перегрузки

// операции <<:

ostream& operator << (ostream& out, employee e)

{

out << "\nВведены следующие сведения о служащем:";

out << "\nИмя: " << e.name;

out << ", оклад: " << e.salery <<" руб.";

out << ", возраст: "<< e.age <<" лет.\n";

return out;

}

Текст этой программы можно взять здесь.

Возможный результат выполнения программы:

Введите сведения о служащем:

Фамилия: Иванов <Enter>

Оклад: 6000 <Enter>

Возраст: 28 <Enter>

Введены следующие сведения о служащем:

Имя: Иванов, оклад: 6000 руб., возраст: 28 лет.

В программе для структуры типа employee перегружены как операция ввода >>, так и операция вывода <<.

На следующем шаге мы рассмотрим функции для обмена с потоками.

Кроме операции включения (записи) в поток << и извлечения (чтения) из потока >>, в классах библиотеки ввода-вывода есть весьма полезные функции, обеспечивающие программиста альтернативными средствами для обмена с потоками.

При выводе в качестве основного класса, формирующего выходные потоки, используется класс ostream. В нем определены (ему принадлежат) две функции для двоичного вывода данных:

ostream& ostream::put (char cc);

ostream& ostream::write (const signed char *array, int n);

ostream& ostream::write (const unsigned char *array, int n);

Функция put() помещает в тот выходной поток, для которого она вызвана, символ, использованный в качестве фактического параметра.

В этом случае эквивалентны операторы:

cout << 'Z';

и

cout.put('Z');

Функция write() имеет два параметра - указатель array на участок памяти, из которого выполняется вывод, и целое значение n, определяющее количество выводимых из этого участка символов (байт).

В отличие от операции << включения в поток функции put() и write() не обеспечивают форматирования выводимых данных. Например, если при выводе одного символа с помощью операции << можно, используя функцию width(), разместить его в поле из нужного количества позиций, то функция put() всегда разместит символ в одной позиции выходного потока. Флаги форматирования также не применимы к функциям put() и write().

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

char ss[] = "Merci";

cout.put('\n').write(ss,sizeof(ss)-l).put('!').put('\n');

На экране (в потоке cout) появится:

Merci!

Если необходимо прочитать из входного потока строку символов, содержащую пробелы, то с помощью операции извлечения >> это делать неудобно - каждое чтение строки выполняется до пробела, а ведущие (левые) пробельные символы игнорируются. Если мы хотим, набрав на клавиатуре строку: "Qui vivra verra - будущее покажет (лат.)", ввести ее в символьный массив, то с помощью операции извлечения >> это сделать несколько хлопотно, все слова будут читаться отдельно (до пробела). Гораздо удобнее воспользоваться функциями бесформатного (двоичного) чтения.

Функции двоичного (бесформатного) чтения данных принадлежат потоку istream. Прежде чем перечислить их, отметим основное свойство двоичного чтения данных. Данные читаются без преобразования их из двоичного представления в текстовое. Например, если во входном потоке размещено представление вещественного числа 1.3E-3, то это будет воспринято как последовательность из шести байт, и читать эту последовательность с помощью функций двоичного ввода можно только в символьный массив.

Итак, функции чтения. Во-первых, это 6 перегруженных функций get(). Две из них имеют следующие прототипы:

istream& get(signed char *array, int max_len, char='\n');

istream& get(unsigned char *array, int max_len, char='\n');

Каждая из этих функций выполняет извлечение (чтение) последовательности байтов из стандартного входного потока и перенос их в символьный массив, задаваемый первым параметром. Второй параметр определяет максимально допустимое количество прочитанных байтов. Третий параметр определяет ограничивающий символ (байт), при появлении которого во входном потоке следует завершить чтение. По умолчанию третий параметр имеет значение '\n' - переход на следующую строку, однако при обращении к функции его можно задавать и по-другому. Значение этого третьего параметра из входного потока не удаляется, он в формируемую строку (символьный массив) не переносится, а вместо него автоматически-добавляется "концевой" символ строки ' \0'. Если из входного потока извлечены ровно maxlen - 1 символов, однако ограничивающий символ (например, по умолчанию '\n') не встретился, то концевой символ помещается после введенных символов. Массив, в который выполняется чтение, должен иметь длину не менее max_len символов. Если из входного потока не извлечено ни одного символа, то устанавливается код ошибки. Если до появления ограничивающего символа и до извлечения max_len - 1 символов встретился конец файла EOF, то чтение прекращается как при появлении ограничивающего символа.

Функция с прототипом

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

извлекает из входного потока символы и помещает их в буфер, определенный первым параметром. Чтение продолжается до появления ограничивающего символа, которым по умолчанию является '\n', но он может быть установлен явно любым образом.

Три следующие варианта функции get() позволяют прочесть из входного потока один символ. Функции

istream& get(unsigned char& cc);

istream& get(signed char& cc);

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

Функция

int get();

получает код извлеченного из потока символа в качестве возвращаемого значения. Если поток пуст, то возвращается код конца файла EOF. Функции "ввода строк":

istream& getline (signed char *array, int len, char='\n');

istream& getline (unsigned char *array, int len, char='\n');

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

Функция

int peek();

позволяет "взглянуть" на очередной символ входного потока. Точнее, она возвращает код следующего символа потока (или EOF, если поток пуст), но оставляет этот символ во входном потоке. При необходимости этот символ можно в дальнейшем извлечь из потока с помощью других среди библиотеки. Например, следующий цикл работает до конца строки (до сигнала от клавиши Enter):

char cim;

while (cin.peek() != '\n')

{ cin.get(cim); cout.put(cim); }

Принадлежащая классу istream функция

istream& putback(char cc);

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

int gcount();

подсчитывает количество символов, которые были извлечены из входного потока при последнем обращении к нему.

Функция

istream& ignore (int n = 1, int = EOF) ;

позволяет извлечь из потока и "опустить" то количество символов n, которое определяется первым параметром. Второй параметр определяет символ-ограничитель, при появлении которого выполнение функции нужно прекратить, даже если из потока еще не извлечены все n символов.

Функции

istream& read (signed char *array, int numb);

istream& read (unsigned char *array, int numb);

выполняют чтение заданного количества numb символов в массив array.

Полезны следующие функции того же класса istream:

istream& seekg (long pos) ;

устанавливает позицию чтения из потока в положение, определяемое значением параметра;

istream& seekg (long pos, seek_dir dir);

выполняет перемещение позиции чтения вдоль потока в направлении, определенном параметром dir, принимающим значения из перечисления enum seek_dir { beg, cur, end }. Относительная величина перемещения (в байтах) определяется значением параметра long pos. Если направление определено как beg, то смещение от начала потока; cur - от текущей позиции; end - от конца потока;

long tellg();

определяет текущую позицию чтения из потока.

Подобные перечисленным функции класса ostream:

long tellp();

определяет текущую позицию записи в поток;

ostream& seekp(long pos, seek_dir dir);

аналогична функции seekg(), но принадлежит классу ostream и выполняет относительное перемещение позиции записи в поток;

ostream& seekp(long pos);

устанавливает абсолютную позицию записи в поток.

Со следующего шага мы начнем рассматривать строковые потоки.

Классы istrstream, ostrstream, strstream, определяемые в заголовочном файле strstream.h (он в компиляторах под MS-DOS имеет более короткое название strstrea.h, так как длина имени файла в MS-DOS не может превышать 8 символов), предназначены в для создания потоков, связанных с участками (областями) основной памяти. Достаточно часто такие участки памяти определяются в программе как символьные массивы. Именно поэтому в обозначении указанных классов используется абревиатура (приставка) "str" - сокращение английского слова string (строка), а объекты этих классов называют строковыми потоками.

Строковый поток определяется и одновременно связывается c областью памяти с помощью конструктора объектов соответствующего класса. Формат вызова конструктора:

имя_класса имя_потока (параметры_конструктора) ;

Имя_класса в данном случае - это одно из имен istrstream, ostrstream, strstream. Имя_потока - это идентификатор (произвольно выбираемое программистом имя объекта данного класса). Типы параметров и их число различны для конструкторов разных классов.

На следующем шаге мы рассмотрим входные строковые потоки.

Входные строковые потоки создаются с помощью такого конструктора класса istrstream:

istrstream имя_потока(char *str);

Обязательным параметром конструктора объектов класса istrstream является указатель str на уже существующий участок основной памяти.

Например, следующие операторы

char buf[40];

istrstream inBuf(buf);

определят входной строковый поток с именем inBuf и свяжут его с участком памяти, предварительно выделенным для символьного массива buf[]. Теперь этот строковый поток inBuf может использоватья как левый операнд операции извлечения >>.

Пример 1. В следующей программе определена строка, содержащая символьную информацию "123.5 Salve!", затем определен и связан с этой строкой входной строковый поток instr. Из потока instr и тем самым из строки, адресуемой указателем stroka, разделенные пробелами значения переносятся в переменную real типа double и символьный массив array[10]. Текст программы:

//OOP13_1.СРР - строковые потоки, операция извлечения >>.

//Автоматически включается файл iostream.h:

#include <strstrea.h>

void main ()

{

// Выделена область памяти (строка):

char *stroka = "123.5 Salve!";

// Создан входной строковый поток instr:

istrstream instr(stroka);

char array [10] ;

double real;

// Извлекаем информацию из строкового потока:

instr >> real >> array;

// Вывод на экран:

cout << "\narray = " << array << " real = " << real << endl;

}

Текст этой программы можно взять здесь.

Результат выполнения программы:

array = Salve! real = 123.5

Пример 2. В следующей программе с помощью входного строкового потока выполняется чтение информации, передаваемой в качестве параметра командной строки функции main:

//OOР13_2.СРР - входной строковый поток; чтение аргумента функции main().

//Автоматически включается файл iostream.h:

#include <strstrea.h>

void main(int argc, char *argv[]) // Определены аргументы.

{

char name[40]; // Выделяется область памяти.

// Создает строковый поток input:

istrstream input(argv[1]);

// Извлекаем информацию из строкового потока:

input >> name;

// Вывод в стандартный поток (на экран):

cout << "\nПри вызове аргумент = " << name << endl;

}

Текст этой программы можно взять здесь.

Если программа будет запущена на выполнение командой:

C:\>OOР13_2.БХЕ Filename <Enter>

то на экране появится сообщение:

При вызове аргумент = FileName

Извлечение информации из строкового потока с помощью операции >> выполняется, начиная от первого непробельного символ до ближайшего пробела. Если необходимо читать и пробельные символы, то можно воспользоваться функциями бесформатного обмена get() и getline(). Эти функции, вызываемые для символьных потоков, позволяют организовать, например, копирование строк. Для копирования строк в библиотеке языка С существует специальная функция strcpy(), определенная в заголовочном файле string.h. Копирование несложно организовать и с помощью циклического переноса символов из одного массива в другой. Однако интересный вариант копирования получается при использовании строковых потоков. Например, в следующей программе выполняется "копирование" содержимого строки, адресуемой указателем line, в заранее созданный символьный массив array[]. В соответствии с форматом функции getline() ее первый параметр - массив, в который ведется чтение, второй параметр - предельное количество читаемых символов, третий параметр - ограничивающий символ, после извлечения которого обмен прекращается. Функция getline() вызывается для чтения из потока inpotok, связанного со строкой line. Текст программы:

//OOР13_3.СРР - копирование строки функцией getline().

#include <strstrea.h>

void main()

{

char *line = "000 111 \t222\n333\t444 555";

istrstream inpotok(line);

char array[80];

inpotok.getline(array,sizeof(array),'\0');

cout << "\narray = " << array << endl;

}

Текст этой программы можно взять здесь.

Результат выполнения программы:

array = 000 111 222

333 444 555

В результатах выполнения обратите внимание на влияние символов '\t', '\n', присутствующих как в исходной строке line, так и перенесенных в символьный массив array. Они, естественно, не выводятся на экран, а обеспечивают табуляцию и смену строки.

На следующем шаге мы рассмотрим безымянные входные строковые потоки.

Конструктор класса istrstream позволяет создавать и связывать с заданной областью памяти безымянные входные строковые потоки. Например, чтение информации, переданной при запуске функции main() в командной строке, обеспечит следующая программа:

//OOР14_1.СРР - безымянный входной строковый поток; чтение

// данных с помощью операции извлечения >>.

#include <strstrea.h>

void main(int Narg, char *arg[])

{

char path [80] ;

// Чтение из безымянного потока:

istrstream(arg[0]) >> path;

cout << "\nПолное имя программы: \n" << path << endl;

}

Текст этой программы можно взять здесь.

Результат выполнения программы, например, такой:

Полное имя программы: D:\WWP\TESTPROG\OOP14_1.EXE

По соглашениям языка и операционной системы arg[0] всегда содержит полное наименование программы с указанием диска и полного пути к каталогу, где находится ЕХЕ-файл. Вызов конструктора istrstream(arg[0]) создает безымянный объект - входной строковый поток, связанный с символьным массивом, адрес которого передается как значение указателя arg[0]. К этому потоку применима операция извлечения >>, с помощью которой полное наименование программы переносится в виде строки из arg[0] в символьный массив path[].

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

//OOР14_2.СРР - чтение из безымянных строковых потоков.

#include <strstrea.h>

void main ()

{

char *line = "000 111 \t222\n333\t444 555";

char array[80]; // Вспомогательный массив/

// Чтение из безымянного потока до пробела:

istrstream (line) >> array;

cout << " \narray = " << array << endl ;

//Повторное чтение из безымянного потока:

istrstream(line) >> array;

cout << "\narray = " << array << endl;

// Вызов функции getline() для безымянного потока:

istrstream(line).getline(array,sizeof(array) , '\0');

cout << "array = " << array << endl;

}

Текст этой программы можно взять здесь.

Результат выполнения программы:

array = 000

array = 000

array = 000 111 222

333 444 555

Из первого безымянного потока данные в массив array извлекаются (до первого пробела) операцией >>. Второй безымянный поток связан с той же строкой. Из него в массив array снова с начала строки читается та же самая информация. Третий безымянный поток читается с помощью функции getline(), которая полностью копирует строку line в символьный массив array[]. Извлечение данных функцией getline() продолжается до появления нулевого признака конца строки '\0'. Этот символ также переносится в массив array и служит признаком конца созданной строки.

На следующем шаге мы рассмотрим выходные строковые потоки.

Предыдущий шаг

Выходные строковые потоки обычно создаются с помощью такого конструктора класса ostrstream:

ostrstream имя_потока(char *str, int len, int mode);

Необязательное имя_потока - это идентификатор, произвольно выбираемый программистом. Указатель str должен адресовать уже существующий участок памяти. Параметр int len определяет размеры этого участка (буфера). Последний параметр - индикатор режима обмена mode. Режим обмена строкового потока при выводе определяет размещение информации в связанной с потоком строке. Для задания конкретного режима используется флаг или дизъюнкция нескольких флагов:

ios::out

строковый поток создается для вывода, запись информации ведется с начала строки;

ios::ate

позиция записи устанавливается в месте размещения нулевого признака конца строки ' \0' (запись в продолжение);

ios::арр

для строковых типов этот флаг действует аналогично флагу ios::ate, но в конкретных реализациях могут быть отличия, обычно отражаемые в документации.

Вместе с флагом ios::out могут быть указаны (в дизъюнктивной форме) флаги ios::ate или ios::арр. В обоих случаях при формировании потока позиция записи устанавливается на нулевой признак '\0' конца строки (буфера потока), т.е. предполагается запись в конец потока.

По умолчанию выбирается ios::out, т.е. строковый поток создается для вывода (записи) информации с начала строки (буфера).

Начнем рассмотрение основных возможностей выходных строковых потоков с их безымянного варианта. В следующей программе значение строки обязательного аргумента функции main с помощью операции вставки << переносится в безымянный строковый поток, связанный с символьным массивом path[]:

//OOР15_1.СРР - запись в безымянный выходной строковый поток (<<).

#include <strstrea.h>