istream &manip_name(istream &stream)
{
// код манипуляторной функции
return stream;
}
Например, в следующей программе создается манипулятор prompt(). Он настраивает входной поток на прием данных в шестнадцатеричном представлении и отображает для пользователя наводящее сообщение.
#include <iostream>
#include <iomanip>
using namespace std;
istream &prompt(istream &stream)
{
cin >> hex;
cout << "Введите число в шестнадцатеричном формате: ";
return stream;
}
Int main()
{
int i;
cin >> prompt >> i;
cout << i;
return 0;
}
Помните: очень важно, чтобы ваш манипулятор возвращал потоковый объект (элемент stream). В противном случае этот манипулятор нельзя будет использовать в составном выражении ввода или вывода.
Файловый ввод-вывод
В С++-системе ввода-вывода также предусмотрены средства для выполнения соответствующих операций с использованием файлов. Файловые операции ввода-вывода можно реализовать после включения в программу заголовка <fstream>, в котором определены все необходимые для этого классы и значения.
Как открыть и закрыть файл
В C++ файл открывается путем связывания его с потоком. Как вы знаете, существуют потоки трех типов: ввода, вывода и ввода-вывода. Чтобы открыть входной поток, необходимо объявить потоковый объект типа ifstream. Для открытия выходного потока нужно объявить поток класса ofstream. Поток, который предполагается использовать для операций как ввода, так и вывода, должен быть объявлен как объект класса fstream. Например, при выполнении следующего фрагмента кода будет создан входной поток, выходной и поток, позволяющий выполнение операций в обоих направлениях.
Ifstream in; // входной поток
Ofstream out; // выходной поток
Fstream both; // поток ввода-вывода
Чтобы открыть файл, используйте функцию open().
Создав поток, его нужно связать с файлом. Это можно сделать с помощью функции open(), причем в каждом из трех потоковых классов есть своя функция-член open(). Представим их прототипы.
void ifstream::open(const char *filename, ios::openmode mode = ios::in);
void ofstream::open(const char *filename, ios::openmode mode = ios::out | ios::trunc);
void fstream::open(const char * filename, ios::openmode mode = ios::in | ios::out);
Здесь элемент filename означает имя файла, которое может включать спецификатор пути. Элемент mode определяет способ открытия файла. Он должен принимать одно или несколько значений перечисления openmode, которое определено в классе ios.
Ios::арр
Ios::ate
Ios::rbinary
Ios::in
Ios::out
Ios::trunc
Несколько значений перечисления openmode можно объединять посредством логического сложения (ИЛИ).
На заметку. Параметр mode для функции fstream::open() может не устанавливаться по умолчанию равным значению in | out (это зависит от используемого компилятора). Поэтому при необходимости этот параметр вам придется задавать в явном виде.
Включение значения ios::арр в параметр mode обеспечит присоединение к концу файла всех выводимых данных. Это значение можно применять только к файлам, открытым для вывода данных. При открытии файла с использованием значения ios::ate поиск будет начинаться с конца файла. Несмотря на это, операции ввода-вывода могут по-прежнему выполняться по всему файлу.
Значение ios::in говорит о том, что данный файл открывается для ввода данных, а значение ios::out обеспечивает открытие файла для вывода данных.
Значение ios::binary позволяет открыть файл в двоичном режиме. По умолчанию все файлы открываются в текстовом режиме. Как упоминалось выше, в текстовом режиме могут происходить некоторые преобразования символов (например, последовательность, состоящая из символов возврата каретки и перехода на новую строку, может быть преобразована в символ новой строки). При открытии файла в двоичном режиме никакого преобразования символов не выполняется. Следует иметь в виду, любой файл, содержащий форматированный текст или еще необработанные данные, можно открыть как в двоичном, так и в текстовом режиме. Единственное различие между этими режимами состоит в преобразовании (или нет) символов.
Использование значения ios::trunc приводит к разрушению содержимого файла, имя которого совпадает с параметром filename, а сам этот файл усекается до нулевой длины. При создании выходного потока типа ofstream любой существующий файл с именем filename автоматически усекается до нулевой длины.
При выполнении следующего фрагмента кода открывается обычный выходной файл.
ofstream out;
out.open("тест");
Поскольку параметр mode функции open() по умолчанию устанавливается равным значению, соответствующему типу открываемого потока, в предыдущем примере вообще нет необходимости задавать его значение.
Не открытый в результате неудачного выполнения функции open() поток при использовании в булевом выражении устанавливается равным значению ЛОЖЬ. Этот факт может служить для подтверждения успешного открытия файла, например, с помощью такой if-инструкции.
if(!mystream) {
cout << "He удается открыть файл.";
// обработка ошибки
}
Прежде чем делать попытку получения доступа к файлу, следует всегда проверять результат вызова функции open().
Можно также проверить факт успешного открытия файла с помощью функции is_open(), которая является членом классов fstream, ifstream и ofstream. Вот ее прототип,
bool is_open();
Эта функция возвращает значение ИСТИНА, если поток связан с открытым файлом, и ЛОЖЬ — в противном случае. Например, используя следующий код, можно узнать, открыт ли в данный момент потоковый объект mystream.
if(!mystream.is_open()) {
cout << "Файл не открыт.";
// ...
}
Хотя вполне корректно использовать функцию open() для открытия файла, в большинстве случаев это делается по-другому, поскольку классы ifstream, ofstream и fstream включают конструкторы, которые автоматически открывают заданный файл. Параметры у этих конструкторов и их значения (действующие по умолчанию) совпадают с параметрами и соответствующими значениями функции open(). Поэтому чаще всего файл открывается так, как показано в следующем примере,
ifstream mystream("myfile"); // файл открывается для ввода
Если по какой-то причине файл открыть невозможно, потоковая переменная, связываемая с этим файлом, устанавливается равной значению ЛОЖЬ.
Чтобы закрыть файл, вызовите функцию close().
Чтобы закрыть файл, используйте функцию-член close(). Например, чтобы закрыть файл, связанный с потоковым объектом mystream, используйте такую инструкцию,
mystream.close();
Функция close() не имеет параметров и не возвращает никакого значения.
Чтение и запись текстовых файлов
Проще всего считывать данные из текстового файла или записывать их в него с помощью операторов "<<" и ">>". Например, в следующей программе выполняется запись в файл test целого числа, значения с плавающей точкой и строки.
// Запись данных в файл.
#include <iostream>
#include <fstream>
using namespace std;
Int main()
{
ofstream out("test");
if(!out) {
cout << "He удается открыть файл.";
return 1;
}
out << 10 << " " << 123.23 << "";
out << "Это короткий текстовый файл.";
out.close();
return 0;
}
Следующая программа считывает целое число, float-значение, символ и строку из файла, созданного при выполнении предыдущей программой.
// Считывание данных из файла.
#include <iostream>
#include <fstream>
using namespace std;
Int main()
{
char ch;
int i;
float f;
char str[80];
ifstream in("test");
if(!in) {
cout << "He удается открыть файл.";
return 1;
}
in >> i;
in >> f;
in >> ch;
in >> str;
cout << i << " " << f << " " << ch << "";
cout << str;
in.close();
return 0;
}
Следует иметь в виду, что при использовании оператора ">>" для считывания данных из текстовых файлов происходит преобразование некоторых символов. Например, "пробельные" символы опускаются. Если необходимо предотвратить какие бы то ни было преобразования символов, откройте файл в двоичном режиме доступа. Кроме того, помните, что при использовании оператора ">>" для считывания строки ввод прекращается при обнаружении первого "пробельного" символа.
Неформатированный ввод-вывод данных в двоичном режиме
Форматированные текстовые файлы (подобные тем, которые использовались в предыдущих примерах) полезны во многих ситуациях, но они не обладают гибкостью неформатированных двоичных файлов. Поэтому C++ поддерживает ряд функций файлового ввода-вывода в двоичном режиме, которые могут выполнять операции без форматирования данных.
Для выполнения двоичных операций файлового ввода-вывода необходимо открыть файл с использованием спецификатора режима ios::binary. Необходимо отметить, что функции обработки неформатированных файлов могут работать с файлами, открытыми в текстовом режиме доступа, но при этом могут иметь место преобразования символов, которые сводят на нет основную цель выполнения двоичных файловых операций.
Функция get() считывает символ из файла, а функция put() записывает символ в файл.
В общем случае существует два способа записи неформатированных двоичных данных в файл и считывания их из файла. Первый состоит в использовании функции-члена put() (для записи байта в файл) и функции-члена get() (для считывания байта из файла). Второй способ предполагает применение "блочных" С++-функций ввода-вывода read() и write(). Рассмотрим каждый способ в отдельности.
Использование функций get() и put()
Функции get() и put() имеют множество форматов, но чаще всего используются следующие их версии:
istream &get(char &ch);
ostream &put(char ch);
Функция get() считывает один символ из соответствующего потока и помещает его значение в переменную ch. Она возвращает ссылку на поток, связанный с предварительно открытым файлом. При достижении конца этого файла значение ссылки станет равным нулю. Функция put() записывает символ ch в поток и возвращает ссылку на этот поток.
При выполнении следующей программы на экран будет выведено содержимое любого заданного файла. Здесь используется функция get().
/* Отображение содержимого файла с помощью функции get().
*/
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char *argv[])
{
char ch;
if(argc!=2) {
cout << "Применение: имя_программы <имя_файла>";
return 1;
}
ifstream in(argv[1], ios::in | ios::binary);
if(!in) {
cout << "He удается открыть файл.";
return 1;
}
while(in) {
/* При достижении конца файла потоковый объект in примет значение false. */
in.get(ch);
if(in) cout << ch;
}
in.close();
return 0;
}
При достижении конца файла потоковый объект in примет значение ЛОЖЬ, которое остановит выполнение цикла while.
Существует более короткий вариант цикла, предназначенного для считывания и отображения содержимого файла.
while(in.get(ch)) cout << ch;
Этот вариант также имеет право на существование, поскольку функция get() возвращает потоковый объект in, который при достижении конца файла примет значение false.
В следующей программе для записи строки в файл используется функция put().
/* Использование функции put() для записи строки в файл.
*/
#include <iostream>
#include <fstream>
using namespace std;
Int main()
{
char *p = "Всем привет!";
ofstream out("test", ios::out | ios::binary);
if(!out) {
cout << "He удается открыть файл.";
return 1;
}
while(*p) out.put(*p++);
out.close();
return 0;
}
Считывание и запись в файл блоков данных
Чтобы считывать и записывать в файл блоки двоичных данных, используйте функции-члены read() и write(). Их прототипы имеют следующий вид.
istream &read(char *buf, streamsize num);
ostream &write(const char *buf, int streamsize num);
Функция read() считывает num байт данных из связанного с файлом потока и помещает их в буфер, адресуемый параметром buf. Функция write() записывает num байт данных в связанный с файлом поток из буфера, адресуемого параметром buf. Как упоминалось выше, тип streamsize определен как некоторая разновидность целочисленного типа. Он позволяет хранить самое большое количество байтов, которое может быть передано в процессе любой операции ввода-вывода.
Функция read() вводит блок данных, а функция write() выводит его.