Приветствую Вас Гость | RSS

ALLDev

Воскресенье, 08.09.2024, 04:05
Главная » Статьи » Программирование » C/C++/C#

Учебник C++. Ввод-вывод

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

В языке Си++ нет особых операторов для ввода или вывода данных. Вместо этого имеется набор классов, стандартно поставляемых вместе с компилятором, которые и реализуют основные операции ввода-вывода.

Причиной является как слишком большое разнообразие операций ввода и вывода в разных операционных системах, особенно графических, так и возможность определения новых типов данных в языке Си++. Вывод даже простой строки текста в MS DOS, MS Windows и в X Window настолько различен, что пытаться придумать общие для всех них операторы было бы слишком негибко и на самом деле затруднило бы работу. Что же говорить о классах, определенных программистом, у которых могут быть совершенно специфические требования к их вводу-выводу.

Библиотека классов для ввода-вывода решает две задачи. Во-первых, она обеспечивает эффективный ввод-вывод всех встроенных типов и простое, но тем не менее гибкое, определение операций ввода-вывода для новых типов, разрабатываемых программистом. Во-вторых, сама библиотека позволяет при необходимости развивать её и модифицировать.

В нашу задачу не входит описание программирования в графических системах типа MS Windows. Мы будем рассматривать операции ввода-вывода файлови алфавитно-цифровой вывод на терминал, который будет работать на консольном окне MS Windows, MS DOS или Unix.

Потоки

Механизм для ввода-вывода в Си++ называется потоком . Название произошло от того,что информация вводится и выводится в виде потока байтов – символ за символом.

Класс istream реализует поток ввода, класс ostream – поток вывода. Эти классы определены в файле заголовков iostream.h. Библиотека потоков ввода-вывода определяет три глобальных объекта: coutcin и cerrcout называется стандартным выводом, cin – стандартным вводом, cerr – стандартным потоком сообщений об ошибках. cout и cerr выводят на терминал и принадлежат к классу ostreamcin имеет тип istream и вводит с терминала. Разница между cout и cerr существенна в Unix – они используют разные дескрипторы для вывода. В других системах они существуют больше для совместимости.

Вывод осуществляется с помощью операции <<, ввод с помощью операции >>. Выражение

cout << "Пример вывода: " << 34;

напечатает на терминале строку "Пример вывода", за которым будет выведено число 34. Выражение

int x;
cin >> x;

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

Операции << и >> для потоков

В классах iostream операции >> и << определены для всех встроенных типов языка Си++ и для строк (тип char* ). Если мы хотим использовать такую же запись для ввода и вывода других классов, определенных в программе, для них нужно определить эти операции.

class String
{
public:
 friend ostream& operator<<(ostream& os, 
 const String& s);
 friend istream& operator>>(istream& is, 
 String& s);
private:
 char* str;
 int length;
};
ostream& operator<<(ostream& os, 
 const String& s)
{
 os << s.str;
 return os;
}
istream& operator>>(istream& is, 
 String& s)
{
// предполагается, что строк длиной более 
// 1024 байтов не будет
 char tmp[1024];
 is >> tmp;

 if (s.str != 0) {
 delete [] s.str;
 }
 size_t length = strlen(tmp);
 s.str = new (std::nothrow) char[length + 1];
 if (s.str == 0) {
 // обработка ошибок
 return is;
 }
 strcpy(s.str, tmp);
 return is;
}

Как показано в примере класса String, операция <<, во-первых, является не методом класса String, а отдельной функцией. Она и не может быть методом класса String, поскольку ее правый операнд – объект класса ostream. С точки зрения записи, она могла бы быть методом класса ostream, но тогда с добавлением нового класса приходилось бы модифицировать класс ostream, что невозможно – каждый бы модифицировал стандартные классы, поставляемые вместе с компилятором. Когда же операция << реализована как отдельная функция, достаточно в каждом новом классе определить ее, и можно использовать запись:

String x;
 . . .
cout << "this is a string: " << x;

Во-вторых, операция << возвращает в качестве результата ссылку на поток вывода. Это позволяет использовать ее в выражениях типа приведенного выше, соединяющих несколько операций вывода в одно выражение.

Аналогично реализована операция ввода. Для класса istream она определена для всех встроенных типов языка Си++ и указателей на строку символов. Если необходимо, чтобы класс, определенный в программе, позволял ввод из потока, для него нужно определить операцию >> в качестве функции friend.

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

Часто бывает необходимо вывести строку или число в определенном формате. Для этого используются так называемые манипуляторы.

Манипуляторы – это объекты особых типов, которые управляют тем, как ostream или istream обрабатывают последующие аргументы. Некоторыеманипуляторы могут также выводить или вводить специальные символы.

С одним манипулятором мы уже сталкивались, это endl. Он вызывает вывод символа новой строки. Другие манипуляторы позволяют задавать формат вывода чисел:

endlпри выводе перейти на новую строку;
endsвывести нулевой байт (признак конца строки символов);
flushнемедленно вывести и опустошить все промежуточные буферы;
decвыводить числа в десятичной системе (действует по умолчанию);
octвыводить числа в восьмеричной системе;
hexвыводить числа в шестнадцатиричной системе счисления;
setw (int n)установить ширину поля вывода в n символов ( n – целое число);
setfill(int n)установить символ-заполнитель; этим символом выводимое значение будет дополняться до необходимой ширины;
setprecision(int n)установить количество цифр после запятой при выводе вещественных чисел;
setbase(int n)установить систему счисления для вывода чисел; n может принимать значения 0, 2, 8, 10, 16, причем 0 означает систему счисления по умолчанию, т.е. 10.

Использовать манипуляторы просто – их надо вывести в выходной поток. Предположим, мы хотим вывести одно и то же число в разных системах счисления:

int x = 53;
cout << "Десятичный вид: " << dec 
 << x << endl
 << "Восьмиричный вид: " << oct 
 << x << endl
 << "Шестнадцатиричный вид: " << hex 
 << x << endl

Аналогично используются манипуляторы с параметрами. Вывод числа с разным количеством цифр после запятой:

double x;
// вывести число в поле общей шириной 
// 6 символов (3 цифры до запятой, 
// десятичная точка и 2 цифры после запятой)
cout << setw(6) << setprecision(2) << fixed << x << endl;

Те же манипуляторы (за исключением endl и ends ) могут использоваться и при вводе. В этом случае они описывают представление вводимых чисел. Кроме того, имеется манипулятор, работающий только при вводе, это ws. Данный манипулятор переключает вводимый поток в такой режим, при котором все пробелы (включая табуляцию, переводы строки, переводы каретки и переводы страницы) будут вводиться. По умолчанию эти символы воспринимаются как разделители между атрибутами ввода.

int x;
// ввести шестнадцатиричное число
cin >> hex >> x;

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

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

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

// произошла ошибка
strstream ss;
ss << "Ошибка ввода-вывода, регистр: " 
 << oct << reg1;
ss << "Системная ошибка номер: " << dec 
 << errno << ends;
String msg(ss.str());
ss.rdbuf()->freeze(0);
Exception ex(Exception::INTERNAL_ERROR, msg);
throw ex;

Сначала создается объект типа strstream с именем ss. Затем в созданный строковый поток выводятся сформатированные нужным образом данные. Отметим, что в конце мы вывелиманипулятор   ends, который добавил необходимый для символьной строки байтов нулевой байт. Метод str() класса strstream предоставляет доступ к сформатированной строке (тип его возвращаемого значения – char* ). Следующая строка освобождает память, занимаемую строковым потоком (подробнее об этом рассказано ниже). Последние две строки создают объект типаException с типом ошибки INTERNAL_ERROR и сформированным сообщением и вызывают исключительную ситуацию.

Важное свойство класса strstream состоит в том, что он автоматически выделяет нужное количество памяти для хранения строк. В следующем примере функция split_numbers выделяет числа из строки, состоящей из нескольких чисел, разделенных пробелом, и печатает их по одному на строке.

#include <strstream.h>
void
split_numbers(const char* s)
{
 strstream iostr;
 iostr << s << ends;
 int x;
 while (iostr >> x) 
 cout << x<< endl;
}
int
main()
{
 split_numbers("123 34 56 932");
 return 1;
}

Замечание. В среде Visual C++ файл заголовков называется strstream.h.

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

Однако из данного правила есть одно исключение. Если программа обращается непосредственно к хранимой в объекте строке с помощью метода str (), то объект перестает контролировать эту память, а это означает, что при уничтожении объекта память не будет освобождена. Для того чтобы память все-таки была освобождена, необходимо вызвать метод rdbuf()->freeze(0) (см. предыдущий пример).

Ввод-вывод файлов

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

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

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

Большинство файлов обладают возможностью прямого доступа. Это означает, что можно производить операции ввода-вывода не последовательно, а в произвольном порядке: после чтения первых 4-х байтов прочесть с 20 по 30, затем два последних и т.п. При написании программ на языке Си++ возможность прямого доступа обеспечивается тем, что текущую позицию чтения или записи можно установить явно.

В библиотеке Си++ для ввода-вывода файлов существуют классы ofstream (вывод) и ifstream (ввод). Оба они выведены из класса fstream. Сами операции ввода-вывода выполняются так же, как и для других потоков – операции >> и << определены для класса fstream как "ввести" и "вывести" соответствующее значение. Различия заключаются в том, как создаются объекты и как они привязываются к нужным файлам.

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

ofstream(const char* szName, 
 int nMode = ios::out,
 int nProt = filebuf::openprot);

Первый аргумент – имя выходного файла, и это единственный обязательный аргумент. Второй аргумент задает режим, в котором открывается поток. Этот аргумент – битовое ИЛИ следующих величин:

ios::appпри записи данные добавляются в конец файла, даже если текущая позиция была перед этим перемещена;
ios::ateпри создании потока текущая позиция помещается в конец файла ; однако, в отличие от режима app, запись ведется в текущую позицию;
ios::inпоток создается для ввода; если файл уже существует, он сохраняется;
ios::outпоток создается для вывода (режим по умолчанию);
ios::truncесли файл уже существует, его прежнее содержимое уничтожается, и длина файла становится равной нулю; режим действует по умолчанию, если не заданы ios::ateios::appили ios::in ;
ios::binaryввод-вывод будет происходить в двоичном виде, по умолчанию используется текстовое представление данных.

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

Можно создать поток вывода с помощью стандартного конструктора без аргументов, а позднее выполнить метод open с такими же аргументами, как у предыдущего конструктора:

void open(const char* szName, 
 int nMode = ios::out,
 int nProt = filebuf::openprot);

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

ostream& write(const char* pch, 
 int nCount);
ostream& put(char ch);

Метод write выводит указанное количество байтов ( nCount ), расположенных в памяти, начиная с адреса pch. Метод put выводит один байт.

Для того чтобы переместить текущую позицию, используется метод seekp:

ostream& seekp(streamoff off, 
 ios::seek_dir dir);

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

ios::begсмещение от начала файла
ios::curсмещение от текущей позиции
ios::endсмещение от конца файла

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

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

Класс ifstream, осуществляющий ввод из файлов, работает аналогично. При создании объекта типа ifstream в качестве аргумента конструктора можно задать имя существующего файла:

ifstream(const char* szName, int nMode = ios::in,
 int nProt = filebuf::openprot);

Можно воспользоваться стандартным конструктором, а подсоединиться к файлу с помощью метода open.

Чтение из файла производится операцией >> или методами read или get:

istream& read(char* pch, int nCount);
istream& get(char& rch);

Метод read вводит указанное количество байтов ( nCount ) в память, начиная с адреса pch. Метод get вводит один байт.

Так же, как и для вывода, текущую позицию ввода можно изменить с помощью метода seekp, а по завершении выполнения операций закрыть файл с помощью close или просто уничтожить объект.

Категория: C/C++/C# | Добавил: artkil (23.09.2012)
Просмотров: 37228 | Комментарии: 5 | Теги: Input, Output, c++ | Рейтинг: 5.0/1
Всего комментариев: 2
2 DavidEsoda  
0
AnabolicsFast Buy Anabolic Steroids Online http://www.anabolicsfast.biz/index.php - Click here!..

1 Progonphons  
0
vase handkerchief ball http://www.casaecomida.com.br/index.php?option=com_k2&view=itemlist&task=user&id=1260141 - - balloon toy ticket http://www.novoshodnensky.org/member.php?action=profile&uid=8283 - w luggage battery bucket http://palmvitamin.com/index.php?option=com_k2&view=itemlist&task=user&id=179706 - z rope board calendar http://www.big-lemon.com/bcms/index.php?option=com_k2&view=itemlist&task=user&id=9913 - 6 laptop brush keyboard http://blog.rubicon-it.ru/194 - f key wheel steering

Имя *:
Email *:
Код *: