До сих пор в программных примерах мы пользовались только функциями стандартной библиотеки С. Однако в C++ имеются собственные средства, основанные на принципах классовой модели. Другими словами, в исполнительной библиотеке C++ имеется набор классов для управления вводом-выводом.
В отличие от функций буферизованного ввода-вывода С (таких, как print f и scanf, не выполняющих никаких проверок на соответствие аргументов форматной строке) классы потоков C++ безопасны в отношении типа. Ввод-вывод использует механизм перегрузки операций, гарантирующий вызов нужной функции-операции для указанного типа данных. Это главное преимущество потоков языка C++.
Классы потоков
К классам потоков относятся следующие:
Класс streambuf управляет буфером потока, обеспечивая базовые операции заполнения, опорожнения, сброса и прочих манипуляций с буфером.
Класс ios является базовым классом потоков ввода-вывода.
Классы istream и ostream — производные от ios и обеспечивают работу потоков соответственно ввода и вывода.
Класс iоstream является производным от двух предыдущих и предусматривает функции как для ввода, так и для вывода.
Классы ifstream, of stream и f stream предназначены для управления файловым вводом-выводом.
Классы istrstream и ostrstream управляют резидентными потоками (форматированием строк в памяти). Это устаревшая методика,оставшаяся в C++Builder в качестве пережитка.
Для работы с потоками вам потребуется включить в программу заголовочный файл iostream.h. Кроме того, может потребоваться подключить файлы fstream.h (файловый ввод-вывод), iomanip.h (параметризованные манипуляторы) и strstream.h (форматирование ь памяти).
Предопределенные потоки
Библиотека ввода-вывода C++ предусматривает четыре предопределенных объекта-потока, связанных со стандартными входным и выходным устройствами. Ниже дана сводка этих объектов.
Таблица 9.1. Предопределенные объекты-потоки C++
Имя
Класс
Описание
cin
istream
Ассоциируется со стандартным вводом (клавиатурой).
cout
ostream
Ассоциируется со стандартным выводом (экраном).
cerr
ostream
Ассоциируется со стандартным устройством ошибок (экраном) без буферизации.
clog
ostream
Ассоциируется со стандартным устройством ошибок (экраном)с буферизацией.
Операции извлечения и передачи в поток
Основными классами ввода-вывода C++ являются istream и ostream. Первый из них перегружает операцию правого сдвига (>>), которая служит в нем для ввода данных и называется операцией извлечения из потока. Класс ostream перегружает соответственно операцию левого сдвига(<<); она применяется для вывода и называется операцией передачи в поток.
Примечание
Нужно сказать, что стандартной русской терминологии как таковой в C++ не существует. Каждый изобретает свою собственную; иногда удачно, иногда — нет.
Вот простейшие операторы ввода и. вывода на стандартных потоках:
#include <iostream.h>
int main()
{
char name [ 8.0] ;
cout<< "Enter your name: ";
cin>> name;
cout <<"Hello " << name << "!";
return 0;
}
Как видите, при действиях над потоками возможно последовательное сцепление операций, подобно последовательному присваиванию. Как вы уже знаете, такая форма записи обеспечивается благодаря тому, что функции-операции извлечения и передачи возвращают ссылку на свой объект.
Перегруженные операции для встроенных типов
Классы istream и ostream перегружают операции соответственно извлечения и передачи в поток для всех встроенных типов. Это позволяет единообразно применять эти операции для чтения и вывода символов, целых, вещественных чисел (т. е. с плавающей точкой) и строк. Вот небольшая иллюстрация, где попутно показан еще простейший прием проверки на ошибку при вводе:
#include <iostream.h>
void check(void) {
if (!cin.good())
{
// Либо просто if (!cin) {
cout << "Error detected!";
exit (1);
}
int main(void)
{
double d;
long 1;
cout << "Enter a floating point value: ";
cin >> d;
check () ;
cout << "You entered: " << d << 'n';
cout << "Enter an integer value: ";
cin >> 1;
check () ;
cout << "You entered: " << 1 << 'n';
return 0;
}
Примечание
Операции извлечения и передачи в поток (соответственно для классов istream и ostream) можно перегрузить таким образом, чтобы можно было применять их для ввода или вывода объектов класса, определенного пользователем. Приведенный ниже пример демонстрирует эту методику. Вообще-то в подобных случаях совершенно необходимо предусмотреть детектирование и обработку ошибок ввода, но здесь мы этого не сделали.
#include <iostream.h>
class Point { int x, у;
public:
Point(int xx = 0, int yy = 0) {
x = xx; у = yy;
}
friend istream &operator>>(istream&, Points);
friend ostream &operator“(ostream&, Points);
};
istream &operator”(istream &is, Point &p)
//
// При вводе точка представляется просто парой чисел,
// разделенных пробелом.
// is >> р.х> р.у;
return is;
}
ostream &operator<<(ostream &os.Point &p) {
//
// Вывод в виде (х, у).
//
os<< ' ( '<< р. х<< ", "<< р. у<<') ' ;
return os;
}
int main() {
Point р;
cout<< "Enter point coordinates: ";
cin>> р;
cout<< "The point values are " << р;
return 0;
}
Форматирование
Библиотека ввода-вывода предусматривает три способа форматирования: посредством вызова форматирующих функций-элементов, с помощью манипуляторов или путем установки или сброса флагов потока.
Форматирующие функции-элементы
Эти функции являются элементами класса ios и перегружены таким образом, чтобы можно было либо читать, либо устанавливать значение соответствующего атрибута потока. Если аргумент в вызове отсутствует, функция возвращает текущее значение атрибута. Если аргумент указан, функция устанавливает новое и возвращает предыдущее значение атрибута.
long width(long)
Эта функция предназначена для чтения или установки атрибута ширины поля.
Применяемая ко входному потоку, функция позволяет задать максимальное число вводимых символов.
На выходном потоке функция задает минимальную ширину поля вывода.
Если действительное поле вывода меньше установленной ширины, выводятся дополнительные заполняющие символы. Символ заполнения определяется специальным атрибутом потока.
Если действительное поле вывода больше установленной ширины, ее значение игнорируется.
Значением ширины по умолчанию является 0 (ширина поля определяется выводимыми данными).
Ширина поля сбрасывается в 0 после каждой передачи в поток.
char fill(char)
Функция позволяет прочитать или установить текущий символ заполнения.
По умолчанию символ заполнения — пробел.
long precision(long)
Эта функция позволяет прочитать или установить значение атрибута точности, определяющего либо общее число выводимых цифр, либо число цифр дробной части.
Точность по умолчанию равна шести цифрам.
Если установлен флаг scientific или fixed, точность задает число цифр после десятичной точки.
Если ни один из этих флагов не установлен, точность задает общее число значащих цифр.
Пример
Ниже приводится программа, демонстрирующая форматирование потока с помощью функций-элементов класса ios.
Листинг 9.1. Демонстрация форматирующих функций потока
Манипуляторы потоков являются по существу функциями, которые можно вызывать непосредственно в цепочке операций извлечения или передачи в поток. Различают простые и параметризованные манипуляторы. У простых манипуляторов аргументы отсутствуют. Параметризованные манипуляторы имеют аргумент.
Ниже приводится сводка имеющихся манипуляторов, как простых, так и параметризованных. Они Перечислены в алфавитном порядке.
Таблица 9.2. Простые и параметризованные манипуляторы
Манипулятор
Описание
dec
Задает десятичную базу преобразования.
end1
Передает в поток символ новой строки и сбрасывает поток.
ends
Передает в поток символ завершающего строку нуля.
flush
Сбрасывает выходной поток.
hex
Задает шестнадцатеричную базу преобразования.
lock(ios Sir)
Блокирует дескриптор файла потока ir.
oct
Задает восьмеричную базу преобразования.
resetiosflags(int f)
Сбрасывает флаги, биты которых установлены в f.
setbase(int b)
Устанавливает базу преобразования (0, 8, 10 или 16).
setiosflags(int f)
Устанавливает флаги, биты которых установлены в f.
setfill(int c)
Задает символ заполнения (аналогичен функции
fiilO).
setprecision(long p)
Задает точность (аналогичен функции precision ()).
setw(iong w)
Задает ширину поля (аналогичен функции width ()).
lunlock(ios &ir)
Разблокирует дескриптор файла для потока ir.
ws
Исключает начальные пробельные символы.
Вот пример использования некоторых манипуляторов (мы создали один свой собственный):
Листинг 9.2. Форматирование с помощью манипуляторов
/////////////////////////////////////////////////
// Manip.cpp: Демонстрация некоторых манипуляторов.
// Format.cpp. cout << "The time is " << setfill('0')
<< setw(2) << h << ':'
<< setw(2) << m << ':'
<< setw(2) << s << setfillC ') << end1;
return 0;
}
Как видите, очень несложно определить свой собственный простой манипулятор. Это всего лишь функция, возвращающая ссылку на переданный ей в параметре поток.
Примечание
Создать параметризованный манипулятор не так просто. Существуют различные способы сделать это, но наиболее очевидный из них — реализация манипулятора через класс эффектора. Идея состоит вот в чем. Нужно определить для манипулятора собственный класс с конструктором, принимающим нужные параметры, •и перегрузить для этого класса операцию передачи (извлечения) соответствующего потока. После этого конструктор можно вызывать в качестве параметризованного манипулятора. Создается временный объект, который выводится в поток перегруженной операцией и удаляется.Ниже показан манипулятор, который выводит в поток свой аргумент типа unsigned в двоичной форме.
#include <iostream.h>
// Класс эффектора.
class Bin {
int val;
public:
Bin(unsigned arg) { val = arg; }
friend ostream &operator“(ostreams. Bin);
};
// Вывод числа в двоичной форме.
ostream &ooerator<<(ostream &os. Bin b) {
int cb = 1; // Контрольный бит для отсчета циклов.
do {
if (b.val <0) // Если val < 0, то старший бит = 1. os << 1;
else
os<< 0;
} while (b.vai<<= 1, cb<<= 1) ;
return os;
}
int main ()
(
unsigned n = Ox00ff0f34;
cout<< "Some binary: "<< Bin(n)<< end1;
return 0;
}
Рис. 9.1Манипулятор, выводящий свой аргумент в двоичной форме
Форматирующие флаги
Флаги управления форматированием являются битовыми полями, хранящимися в переменной типа fmtflags (псевдоним int). Для их чтения и/или модификации могут применяться следующие функции-элементы класса ics:
int flags (), int flags (int). Без параметра возвращает текущее состояние флагов. При указанном параметре устанавливает новые значения флагов и возвращает их прежнее состояние.
int setf(int), long setf(int, int). Первая форма устанавливает флаги, биты которых установлены в параметре. Вторая форма модифицирует флаги, биты которых установлены во втором параметре. Значения этих флагов задаются первым параметром. Возвращает прежнее состояние всех флагов.
void unsetf(int). Сбрасывает флаги, биты которых установлены в параметре.
Помимо функций, для управления флагами можно пользоваться манипуляторами setiosflags (аналог setf() с одним параметром) и reset-iosflags (аналог unsetf ()).
В таблице 9.3 описаны форматирующие флаги потоков.
Таблица 9.3. Форматирующие флаги класса ios
Флаг
Описание
internal
Если установлен, при выводе чисел знак выводится на левом краю поля вывода, а само число выравнивается по правому краю поля. Промежуток заполняется текущим символом заполнения.
dec
Устанавливает десятичное представление чисел. Принимается по умолчанию.
oct
Устанавливает восьмеричное представление чисел.
hex
Устанавливает шестнадцатеричное представление чисел.
showbase
Если установлен, то при восьмеричном и шестнадцатеричном представлении чисел выводит индикатор основания (0 для восьмеричных и Ох для шестнадцатеричных чисел).
showpoint
Если установлен, для вещественных чисел всегда выводится десятичная точка.
uppercase
Если установлен, шестнадцатеричные цифры от А до F, а также символ экспоненты Е выводятся в верхнем регистре.
boolalpfa
Если установлен, булевы значения выводятся как слова “true/false”. В противном случае они представляются соответственно единицей и нулем.
showpos
Выводит + для положительных чисел.
scientific
Если установлен, вещественные числа выводятся в научной (экспоненциальной) нотации.
fixed
Если установлен, вещественные числа выводятся в десятичном формате (с фиксированной точкой).
unitbuf
Если установлен, поток сбрасывается после каждой операции передачи.
Несколько замечаний относительно перечисленных в таблице флагов.
Флаги left, right и internal являются взаимоисключающими. В данный момент времени может быть установлен только один из них.
Взаимоисключающими являются также флаги dec, oct и hex.
При модификации базы представления в качестве второго параметра setf() можно использовать константу ios: :basefield.
При модификации выравнивания в поле можно аналогичным образом использовать константу ios: :adjustfield.
При модификации формы представления (нотации) чисел с плавающей точкой можно использовать константу ios : : floatfield. Ниже мы приводим листинг программы, демонстрирующей применение различных флагов форматирования.
Примечание
Имена перечисленных выше флагов и других констант принадлежат к области действия класса ios. Вне этого класса нужно либо воспользоваться разрешением области действия (ios : : scientific), либо обращаться к ним, как к элементам существующего объекта (cout. scientific). Мы поедпочитаем первый способ.
Состояние объекта класса ios (и производных от него) содержится в его закрытом элементе _state в виде набора битов. Следующая таблица перечисляет имеющиеся биты состояния потока.
Таблица 9.4. Биты состояния потока
Бит
Описание
goodbit
С потоком все в порядке (на самом деле это не какой-то бит, а 0 — отсутствие битов ошибки).
eofbit
Показывает, что достигнут конец файла.
failbit
Индицирует ошибку формата или преобразования. После очистки данного бита работа с потоком может быть продолжена.
badbit
Индицирует серьезную ошибку потока, связанную обычно с буферными операциями или аппаратурой. Скорее всего, поток далее использовать невозможно.
Для опроса или изменения состояния потока в классе ios имеется ряд функций и операций.
int rdstate() ; Возвращает текущее состояние.
bool eof() ; Возвращает true, если установлен eofbit.
bool good () ; Возвращает true, если не установлен ни один из битов ошибки.
bool fail () ; Возвращает true, если установлен failbit или bad-bit.
bool bad() ; Возвращает true, если установлен badbit.
void clear (int =0); Сбрасывает биты ошибки (по умолчанию) или устанавливает состояние потока в соответствии с аргументом.
void setstate(int) ; Устанавливает состояние битов ошибки с соответствии с аргументом.
operator void*() ; Возвращает нулевой указатель, если установлен какой-либо из битов ошибки.
bool operator! () ; Возвращает true, если установлен какой-либо из битов ошибки.
Примечание
Функция operator void*() неявно вызывается, если поток сравнивается с нулем (как cin в примере из листинга),
Файловые потоки
Файловые потоки библиотеки ввода-вывода реализуют объектно-ориентированную методику работы с дисковыми файлами. Имеется три класса таких потоков:
ifstream специализирован для ввода из дисковых файлов.
of stream специализирован для записи дисковых файлов.
fstream управляет как вводом, так и записью на диск.
Эти классы выводятся соответственно из istream, ostream и iostream. Таким образом, они наследуют все их функциональные возможности (перегруженные операции << и>>” для встроенных типов, флаги форматирования и состояния, манипуляторы и т. д.).
Чтобы работать с файловым потоком, нужен, во-первых, объект потока, а во-вторых, открытый файл, связанный с этим объектом.
Конструирование объекта потока
Каждый из трех классов файловых потоков имеет четыре конструктора.
Конструктор, создающий объект без открытия файла:
ifstream () ;
of stream();
fstream () ;
Конструктор, создающий объект, открывающий указанный файл и закрепляющий этот файл за потоком. Аргументами являются имя файла, режим открытия и режим защиты (в Windows не используется):
if stream(const char *name,
int mode = ios::in, long prot = 0666);
ofstream(const char *name,
int mode = ios::out, long prot = 0666);
fstream (const char *name, int mode, long prot = 0666);
Конструктор, создающий объект и связывающий с ним уже открытый файл. В качестве аргумента передается дескриптор файла:
ifstreamfint file);
ofstream(int file);
fstream (int file) ;
Конструктор, создающий объект и связывающий с ним уже открытый файл; объект ассоциируется указанным буфером:
ifstream(int file, char *buf, int len)
of stream(int file, char *buf, int len)
fstream (int file, char *buf, int len)
Режимы открытия файла
Параметр mode, который имеет вторая форма конструктора, задает режим открытия файла. Для значений параметра класс ios определяет символические константы, перечисленные в таблице 9.5.
Таблица 9.5. Константы класса ios для режимов открытия файла
Константа
Описание
арр
Открытие для записи в конец файла.
ate
При открытии позиционирует указатель на конец файла.
binary
Файл открывается в двоичном (не текстовом) режиме.
in
Файл открывается для ввода.
out
Файл открывается для вывода.
trunc
Если файл существует, его содержимое теряется.
Константы можно комбинировать с помощью поразрядного OR. Для конструкторов классов if stream и ofstream параметр mode имеет значения по умолчанию — соответственно ios : : in и ios : : out.
Закрытие файла
В классах файловых потоков имеется функция close (), которая сбрасывает содержимое потока и закрывает ассоциированный с ним файл.
Кроме того, деструктор потока автоматически закрывает файл при уничтожении объекта потока.
При ошибке закрытия файла устанавливается флаг failbit.
Примеры файловых потоков
Следующий пример (листинг 9.4) демонстрирует различные режимы и способы открытия потока.
char *data[] = {"It's the first line of test data.",
"Second ,line.",
"Third line.",
"That's enough!"};
//
// Функция для распечатки содержимого файла. //
int Print(char *fn) {
char buf[80] ;
ifstream ifs(fn) ;
if (!ifs) {
cout <<fn<< " - Error reading file." << endl;
return -1;
} while (ifs) {
ifs.getline(buf, sizeof(buf)) ;
if (ifs)
cout << buf<< end1;
} return 0;
}
#pragma argsused
int main(int argc, char* argv[])
{
char name[]= "Newfile.txt";
fstream fs(name, ios::in);
if (fs) { // Файл уже существует. cout “ name “ " - File already exists." << endl;
} else { // Создать новый файл.
cout<< name<< " - Creating new file."<< endl;
fs.open(name, ios::out);
for (int i=0; i<3; i++) fs << data[i] << endl;
}
fs.close () ;
cout << end1;
//
// Файл либо уже существовал, либо мы его только что
// создали. Распечатаем его.
// Print(name);
cout << endl;
//
// Допишем строку в конец файла.
// fs.open(name, ios::app);
if (rs) {
fs M<< data[3]<< endl;
fs.close ();
} Print(name);
return 0;
}
Рис. 9.3Результат работы программы Filemode
Для чтения строки из файла мы применили в программе функцию getline () , которая будет подробно описана чуть позже.
Бесформатный ввод-вывод
До сих пор речь у нас шла почти исключительно о вводе-выводе с использованием операций извлечения/передачи данных. Эти операции перегружены для всех встроенных типов и выполняют соответствующие преобразования из внутреннего представления данных в текстовое и из текстового во внутреннее (машинное).
Однако в библиотеке C++ имеется немало функций бесформатного ввода-вывода, которые часто применяют для чтения и записи двоичных (не-текстовых) файлов.
Двоичный режим ввода-вывода
Двоичный режим открытия файла (с установленным битом binary) означает, что никакой трансляции данных при передаче из файла в поток и обратно производиться не будет. Речь здесь идет не о форматных преобразованиях представления данных. При текстовом режиме (он принимается по умолчанию) при передаче данных между файлом и потоком производится замена пар символов CR/LF на единственный символ LF (' n ') и наоборот. Это происходит до преобразований представления, которые выполняются операциями извлечения/передачи. Двоичный ввод-вывод означает всего-навсего, чтотакой замены происходить не будет; тем не менее двоичный режим необходим при работе с сырыми данными, т. е. данными в машинной форме без преобразования их в текстовый формат.
Чтобы открыть файл в двоичном режиме, нужно, как уже упоминалось, установить в параметре mode конструктора потока или функции open() бит ios::binary.
Чтение и запись сырых данных
Чтение сырых данных производится функцией read () класса istream:
istream &read(char *buf, long len);
Здесь buf — адрес буфера, в который будут читаться данные, а len — число символов, которые нужно прочитать.
Запись сырых данных производится функцией write () класса ostream. Она выглядит точно так же, как функция read () :
ostream &write(char *buf, long len);
Здесь buf — адрес буфера, в котором содержатся данные, а len — число символов, которые должны быть записаны в поток.
Обе функции возвращают ссылку на свой объект-поток. Это означает, что возможны их цепные вызовы, т. е. выражения вроде
ostream os (...);
os.write(...).write (...).write(...) ;
Вот небольшой пример записи и чтения сырых данных: