Заголовочные файлы. Предопределенные объекты и потоки. Операции помещения и извлечения. Форматирование. Флаги форматирования. Манипуляторы. Ошибки потоков. Файловый ввод-вывод с применением потоков С++. Конструкторы файловых потоков. Открытие файлов в разных режимах. Ввод-вывод в файлы. Заголовочные файлы. В Си имеется общая технология, которая касается как организации модульных программ, так и библиотек. Любой модуль, который претендует на дальнейшее использование через обращение к собственным внешним переменным и вызов собственных внешних функций, должен иметь некоторое описание своего интерфейса. Оно заключается в составлении заголовочного файла (файла с расширением - ".h"), который используется другими модулями. Заголовочный файл должен включать в себя:
определение используемых типов данных в формальных параметрах и результатах функций с использованием оператора typedef;
объявления внешних переменных и функций модуля, к которым возможно обращение.
При помощи директивы #include текст заголовочного файла включается в текст транслируемого модуля, таким образом транслятор получает необходимые определения типов и объявления переменных и функций модуля, к которым будут производиться обращения. Директива #include возможна в двух вариантах:
#include - заголовочный файл из системного каталога
#include "myhead.h" - заголовочный файл из текущего (явно указанного) каталога
Процесс подготовки библиотеки включает в себя следующие шаги
создание заголовочного файла, содержащего определения используемых типов данных и объявления внешних функций и переменных библиотеки;
создание модуля, включающего определение функций и переменных библиотеки и трансляция его в объектный модуль;
включение объектного модуля в библиотеку.
Предопределенные объекты и потоки. В стандартной библиотеке ввода/вывода стандартного Си (заголовочный файл библиотеки - ) имеются внешние переменные-указатели на дескрипторы файлов - стандартных устройств ввода-вывода.
extern FILE *stdin, *stdout, *stderr, *stdaux, *stdprn;
стандартный ввод
стандартный вывод
регистрация ошибок
дополнительное устройство
устройство печати
Эти файлы открываются библиотекой автоматически перед выполнением функции main и по умолчанию назначаются на терминал (stdin - клавиатура, stdout, stderr - экран), последовательный порт (stdaux) и принтер (stdprn). stdin и stdout могут быть переназначены в командной строке запуска программы на любые другие файлы
>test.exe c:\xxx\b.dat
файл stdin файл stdout
В Си++ существуют классы потоков ввода-вывода, которые являются объектно-ориентированным эквивалентом (stream.h) стандартной библиотеки ввода-вывода (stdio.h):
ios базовый потоковый класс
streambuf буферизация потоков
istream потоки ввода
ostream потоки вывода
iostream двунаправленные потоки
iostream_withassign поток с переопределенной операцией присваивания
istrstream строковые потоки ввода
ostrstream строковые потоки вывода
strstream двунаправленные строковые потоки
ifstream файловые потоки ввода
ofstream файловые потоки вывода
fstream двунаправленные файловые потоки
Стандартные потоки (istream,ostream,iostream) служат для работы с терминалом.
Строковые потоки (istrstream, ostrstream, strstream) служат для ввода-вывода из строковых буферов, размещенных в памяти.
Файловые потоки (ifstream, ofstream, fstream) служат для работы с файлами.
Следующие объекты-потоки заранее определены и открыты в программе перед вызовом функции main:
extern istream cin; // Стандартный поток ввода с клавиатуры
extern ostream cout; // Стандартный поток вывода на экран
extern ostream cerr; // Стандартный поток вывода сообщений об ошибках (экран)
extern ostream cerr;// Стандартный буферизованный поток вывода сообщений об ошибках (экран).
Операции помещения и извлечения. Для начала рассмотрим пример:
#include
main()
{
cout << "Hello, world\n";
};
Строка #include сообщает компилятору, чтобы он включил стандартные возможности потока ввода и вывода, находящиеся в файле stream.h. Без этих описаний выражение cout << "Hello, world\n" не имело бы смысла. Операция << ("поместить в") пишет свой первый аргумент во второй (в данном случае, строку "Hello, world\n" в стандартный поток вывода cout). Программирующим на C << известно как операция сдвига влево для целых. Такое использование << не утеряно, просто в дальнейшем << было определено для случая, когда его левый операнд является потоком вывода.
Ввод производится с помощью операции >> ("извлечь из") над стандартным потоком ввода cin. Описания cin и >>, конечно же, находятся в <stream.h>. Операцию вывода << можно применять к ее собственному результату, так что несколько команд вывода можно записать одним оператором:
cout << inch << " in = " << inch*2.54 << " cm\n";
Операция вывода используется, чтобы избежать той многословности, которую дало бы использование функции вывода. Но почему <<? Потому что возможности изобрести новый лексический символ не существует. Операция присваивания была кандидатом одновременно и на ввод, и на вывод, но оказывается, большинство людей предпочитают, чтобы операция ввода отличалась от операции вывода. Кроме того, = не в ту сторону связывается (ассоциируется), то есть cout=a=b означает cout=(a=b).
Делались попытки использовать операции < и >, но значения "меньше" и "больше" настолько прочно вросли в сознание людей, что новые операции ввода/вывода во всех реальных случаях оказались нечитаемыми. Помимо этого, "<" находится на большинстве клавиатур как раз на ",", и у людей получаются операторы вроде такого:
cout < x , y , z;
Для таких операторов непросто выдавать хорошие сообщения об ошибках.
Операции << и >> к такого рода проблемам не приводят, они асимметричны в том смысле, что их можно проассоциировать с "в" и "из", а приоритет << достаточно низок для того, чтобы можно было не использовать скобки для арифметических выражений в роли операндов.
Например:
cout << "a*b+c=" << a*b+c << "\n";
Естественно, при написании выражений, которые содержат операции с более низкими приоритетами, скобки использовать надо. Например:
cout << "a^b|c=" << (a^b|c) << "\n";
Операцию левого сдвига тоже можно применять в операторе вывода:
cout << "a<
В С++ нет выражений с символьными значениями, в частности, '\n' является целым (со значением 10, если используется набор символов ASCII), поэтому
cout << "x = " << x << '\n';
напечатает число 10, а не ожидаемый символ новой строки. Эту и аналогичные проблемы можно сгладить, определив несколько макросов (в которых используются стандартные имена символов ASCII):
#define sp << " "
#define ht << "\t"
#define nl << "\n"
Теперь предыдущий пример запишется в виде:
cout << "x = " << x nl;
Для печати символов предоставляются функции ostream::put(char) и chr(int). Рассмотрим примеры:
cout << x << " " << y << " " << z << "\n";
cout << "x = " << x << ", y = " << y << "\n";
Люди находят их трудно читаемыми из-за большого числа кавычек и того, что операция вывода внешне выглядит слишком непривычно. Здесь могут помочь приведенные выше макросы и несколько отступов:
cout << x sp << y sp << z nl;
cout << "x = " << x
<< ", y = " << y nl;
Форматирование. Флаги форматирования. Пока << применялась только для неформатированного вывода, и в реальных программах она именно для этого главным образом и применяется. В то же время << легко справляется с тремя стандартными типами данных: char, int, float.В резудьтате переопределения операция помещения в поток определяет тип посланных данных и сама выбирает подходящий формат. То же самое происходит и с операцией извлечения из потока >>, которая вводит данные. Сравним пример на Си:
scanf("%d%f%c", &int_data, &float_data, &char_data);
с его эквивалентом на Си++:
cin >> int_data >> float_data >> char_data;
В Си++ не нужно заботиться о том, чтобы ставить перед переменными знак адреса &. Операция извлечения из потока сама вычисляет адреса переменных, определяет их типы и формат ввода.
Помимо этого существует также несколько форматирующих функций, создающих представление своего параметра в виде строки, которая используется для вывода. Их второй (необязательный) параметр указывает, сколько символьных позиций должно использоваться.
char* oct(long, int=0); // восьмеричное представление
char* dec(long, int=0); // десятичное представление
char* hex(long, int=0); // шестнадцатеричное представление
char* chr(int, int=0); // символ
char* str(char*, int=0); // строка
Если не задано поле нулевой длины, то будет производиться усечение или дополнение; иначе будет использоваться столько символов (ровно), сколько нужно. Например:
cout << "dec(" << x
<< ") = oct(" << oct(x,6)
<< ") = hex(" << hex(x,4)
<< ")";
Если x = 15, то в результате получится:
dec(15) = oct( 17) = hex( f);
Можно также использовать строку в общем формате:
char* form(char* format ...);
cout< cout<ostream()
{
flush(); // сброс
};
Сбросить буфер можно также и явно. Например:
cout.flush();
Открытие файлов в разных режимах. Точные детали того, как открываются и закрываются файлы, различаются в разных операционных системах. Поскольку после включения <stream.h> становятся доступны cin, cout и cerr, во многих (если не во всех) программах не нужно держать код для открытия файлов. Вот, однако, программа, которая открывает два файла, заданные как параметры командной строки, и копирует первый во второй:
#include
void error(char* s, char* s2)
{
cerr << s << " " << s2 << "\n";
exit(1);
}; main(int argc, char* argv[])
{
if (argc != 3) error("неверное число параметров","");
filebuf f1;
if (f1.open(argv[1],input) == 0)
error("не могу открыть входной файл",argv[1]);
istream from(&f1);
filebuf f2;
if (f2.open(argv[2],output) == 0)
error("не могу создать выходной файл",argv[2]);
ostream to(&f2);
char ch;
while (from.get(ch)) to.put(ch);
if (!from.eof() && to.bad())
error("случилось нечто странное","");
};
Последовательность действий при создании ostream для именованного файла та же, что используется для стандартных потоков:
сначала создается буфер (здесь это делается посредством описания filebuf);
затем к нему подсоединяется файл (здесь это делается посредством открытия файла с помощью функции filebuf::open());
создается сам ostream с filebuf в качестве параметра.
Потоки ввода обрабатываются аналогично.
Файл может открываться в одной из двух мод:
enum open_mode { input, output };
Действие filebuf::open() возвращает 0, если не может открыть файл в соответствие с требованием. Если пользователь пытается открыть файл, которого не существует для output, то он будет создан.
Используя классы ifstream и ofstream, производные от istream и ostream, описанные в fstream.h, можно открывать файловые потоки в разных модах с помощью флагов конструктора потока:
ofstream object (filename, flag)
где flag может иметь следующие значения:
ios::app запись в конец существующего файла
ios::ate после открытия файла перейти в его конец
ios::binary открыть файл в двоичном режиме (по умолчанию - текстовый)
ios::in открыть для чтения
ios::nocreate сообщать о невозможности открытия, если файл не существует
ios::noreplace сообщать о невозможности открытия, если файл существует
ios::out открыть для вывода
ios::trunc если файл существует, стереть содержимое.
При необходимости изменить способ открытия или применения файла можно при создании файлового потока использовать два флага или более флага: ios::app|ios::noreplace
Для открытия файла одновременно на чтение и запись можно использовать объекты класса fstream:
fstream object(filename, ios::in|ios::app);
Перед завершением программа проверяет, находятся ли потоки в приемлемом состоянии. При завершении программы открытые файлы неявно закрываются. Для явного закрытия объектов файловых потоков применяется метод close():
object.close();
Ввод-вывод в файлы. Для ввода/вывода в потоковые объекты можно применять методы put(), get(), для связывания объекта с различными файлами служат методы open(), close(), для позиционирования в файле имеются методы seekg(), seekp(), tellp(). При этом seekg() назначает или возвращает текущую позицию указателя чтения, а seekp() назначает или возвращает текущую позицию указателя записи. Обе функции могут иметь один или два аргумента. При вызове с одним аргументом функции перемещают указатель на заданное место, а при вызове с двумя аргументами вычисляется относительная позиция от начала файла (ios::beg), текущей позиции (ios::cur) или от конца файла (ios::end). Текущая позиция определяется методом tellp().
Для объектов файловых потоков контроль состояния также производится с помощью методов, манипулирующих флагами ошибок:
bad() возвращает ненулевое значение, если обнаружена ошибка
clear() сбрасывает сообщения об ошибках
eof() возвращает ненулевое значение, если обнаружен конец файла
fail() возвращает ненулевое значение, если операция завершилась неудачно
good() возвращает ненулевое значение, если флаги ошибок не выставлены
rdstate() возвращает текущее состояние флагов ошибок.
Если флаги показывают наличие ошибки, все попытки поместить в поток новые объекты будут игнорироваться, то есть состояние потока не изменится.
|