Бьерн Страуструп (947334), страница 58
Текст из файла (страница 58)
ostream& flush(ostream&);
а в классе есть операция operator<<, которая использует указатель на
функцию, как указано выше:
class ostream : public virtual ios {
// ...
public:
ostream& operator<<(ostream& ostream& (*)(ostream&));
// ...
};
В приведенной ниже строке буфер выталкивается в поток cout дважды в
подходящее время:
cout << x << flush << y << flush;
Похожие определения существуют и для класса istream:
istream& ws(istream& is ) { return is.eatwhite(); }
class istream : public virtual ios {
// ...
public:
istream& operator>>(istream&, istream& (*) (istream&));
// ...
};
поэтому в строке
cin >> ws >> x;
действительно обобщенные пробелы будут убраны до попытки чтения в x.
Однако, поскольку по умолчанию для операции >> пробелы "съедаются" и
так, данное применение ws() избыточно.
Находят применение и манипуляторы с параметрами. Например,
может появиться желание с помощью
cout << setprecision(4) << angle;
напечатать значение вещественной переменной angle с точностью до
четырех знаков после точки.
Для этого нужно уметь вызывать функцию, которая установит
значение переменной, управляющей в потоке точностью вещественных.
Это достигается, если определить setprecision(4) как объект, который
можно "выводить" с помощью operator<<():
class Omanip_int {
int i;
ostream& (*f) (ostream&,int);
public:
Omanip_int(ostream& (*ff) (ostream&,int), int ii)
: f(ff), i(ii) { }
friend ostream& operator<<(ostream& os, Omanip& m)
{ return m.f(os,m.i); }
};
Конструктор Omanip_int хранит свои аргументы в i и f, а с помощью
operator<< вызывается f() с параметром i. Часто объекты таких классов
называют объект-функция. Чтобы результат строки
cout << setprecision(4) << angle
был таким, как мы хотели, необходимо чтобы обращение setprecision(4)
создавало безымянный объект класса Omanip_int, содержащий значение 4
и указатель на функцию, которая устанавливает в потоке ostream значение
переменной, задающей точность вещественных:
ostream& _set_precision(ostream&,int);
Omanip_int setprecision(int i)
{
return Omanip_int(&_set_precision,i);
}
Учитывая сделанные определения, operator<<() приведет к вызову
precision(i).
Утомительно определять классы наподобие Omanip_int для всех
типов аргументов, поэтому определим шаблон типа:
template<class T> class OMANIP {
T i;
ostream& (*f) (ostream&,T);
public:
OMANIP(ostream (*ff) (ostream&,T), T ii)
: f(ff), i(ii) { }
friend ostream& operator<<(ostream& os, OMANIP& m)
{ return m.f(os,m.i) }
};
С помощью OMANIP пример с установкой точности можно сократить так:
ostream& precision(ostream& os,int)
{
os.precision(i);
return os;
}
OMANIP<int> setprecision(int i)
{
return OMANIP<int>(&precision,i);
}
В файле <iomanip.h> можно найти шаблон типа OMANIP, его двойник для
istream - шаблон типа SMANIP, а SMANIP - двойник для ioss.
Некоторые из стандартных манипуляторов, предлагаемых поточной
библиотекой, описаны ниже. Отметим,что программист может определить новые
необходимые ему манипуляторы, не затрагивая определений istream,
ostream, OMANIP или SMANIP.
Идею манипуляторов предложил А. Кениг. Его вдохновили процедуры
разметки (layout ) системы ввода-вывода Алгола68. Такая техника имеет
много интересных приложений помимо ввода-вывода. Суть ее в том, что
создается объект, который можно передавать куда угодно и который
используется как функция. Передача объекта является более гибким
решением, поскольку детали выполнения частично определяются создателем
объекта, а частично тем, кто к нему обращается.
10.4.2.1 Стандартные манипуляторы ввода-вывода
Это следующие манипуляторы:
// Simple manipulators:
ios& oct(ios&); // в восьмеричной записи
ios& dec(ios&); // в десятичной записи
ios& hex(ios&); // в шестнадцатеричной записи
ostream& endl(ostream&); // добавить '\n' и вывести
ostream& ends(ostream&); // добавить '\0' и вывести
ostream& flush(ostream&); // выдать поток
istream& ws(istream&); // удалить обобщенные пробелы
// Манипуляторы имеют параметры:
SMANIP<int> setbase(int b);
SMANIP<int> setfill(int f);
SMANIP<int> setprecision(int p);
SMANIP<int> setw(int w);
SMANIP<long> resetiosflags(long b);
SMANIP<long> setiosflags(long b);
Например,
cout << 1234 << ' '
<< hex << 1234 << ' '
<< oct << 1234 << endl;
напечатает
1234 4d2 2322
и
cout << setw(4) << setfill('#') << '(' << 12 << ")\n";
cout << '(' << 12 << ")\n";
напечатает
(##12)
(12)
Не забудьте включить файл <iomanip.h>, если используете манипуляторы с
параметрами.
10.4.3 Члены ostream
В классе ostream есть лишь несколько функций для управления выводом,
большая часть таких функций находится в классе ios.
class ostream : public virtual ios {
//...
public:
ostream& flush();
ostream& seekp(streampos);
ostream& seekp(streamoff, seek_dir);
streampos tellp();
//...
};
Как мы уже говорили, функция flush() опустошает буфер в выходной поток.
Остальные функции используются для позиционирования в ostream при
записи. Окончание на букву p указывает, что именно позиция используется
при выдаче символов в заданный поток. Конечно эти функции имеют смысл,
только если поток присоединен к чему-либо, что допускает
позиционирование, например файл. Тип streampos представляет позицию символа
в файле, а тип streamoff представляет смещение относительно позиции,
заданной seek_dir. Все они определены в классе ios:
class ios {
//...
enum seek_dir {
beg=0, // от начала файла
cur=1, // от текущей позиции в файле
end=2 // от конца файла
};
//...
};
Позиции в потоке отсчитываются от 0, как если бы файл был массивом из
n символов:
char file[n-1];
и если fout присоединено к file, то
fout.seek(10);
fout<<'#';
поместит # в file[10].
10.4.4 Члены istream
Как и для ostream, большинство функций форматирования и управления
вводом находится не в классе iostream, а в базовом классе ios.
class istream : public virtual ios {
//...
public:
int peek()
istream& putback(char c);
istream& seekg(streampos);
istream& seekg(streamoff, seek_dir);
streampos tellg();
//...
};
Функции позиционирования работают как и их двойники из ostream.
Окончание на букву g показывает, что именно позиция используется при
вводе символов из заданного потока. Буквы p и g нужны, поскольку
мы можем создать производный класс iostreams из классов ostream и
istream, и в нем необходимо следить за позициями ввода и вывода.
С помощью функции peek() программа может узнать следующий символ,
подлежащий вводу, не затрагивая результата последующего чтения. С
помощью функции putback(), как показано в $$10.3.3, можно вернуть
ненужный символ назад в поток, чтобы он был прочитан в другое время.
10.5 Файлы и потоки
Ниже приведена программа копирования одного файла в другой. Имена
файлов берутся из командной строки программы:
#include <fstream.h>
#include <libc.h>
void error(char* s, char* s2 ="")
{
cerr << s << ' ' << s2 << '\n';
exit(1);
}
int main(int argc, char* argv[])
{
if (argc != 3) error("wrong number of arguments");
ifstream from(argv[1]);
if (!from) error("cannot open input file",argv[1]);
ostream to(argv[2]);
if (!to) error("cannot open output file",argv[2]);
char ch;
while (from.get(ch)) to.put(ch);
if (!from.eof() || to.bad())
error("something strange happened");
return 0;
}
Для открытия выходного файла создается объект класса ofstream -
выходной поток файла, использующий в качестве аргумента имя файла.
Аналогично, для открытия входного файла создается объект класса
ifstream - входной файловый поток, также использующий в качестве
аргумента имя файла. В обоих случаях следует проверить состояние
созданного объекта, чтобы убедиться в успешном открытии файла, а
если это не так, операции завершатся не успешно, но корректно.
По умолчанию ifstream всегда открывается на чтение, а ofstream
открывается на запись. В ostream и в istream можно использовать
необязательный второй аргумент, указывающий иные режимы открытия:
class ios {
public:
//...
enum open_mode {
in=1, // открыть на чтение
out=2, // открыть как выходной
ate=4, // открыть и переместиться в конец файла
app=010, // добавить
trunc=020, // сократить файл до нулевой длины
nocreate=040, // неудача, если файл не существует
noreplace=0100 // неудача, если файл существует
};
//...
};
Настоящие значения для open_mode и их смысл вероятно будут зависеть
от реализации. Будьте добры, за деталями обратитесь к руководству по
вашей библиотеке или экспериментируйте. Приведенные комментарии
могут прояснить их назначение. Например, можно открыть файл с условием,
что операция открытия не выполнится, если файл уже не существует:
void f()
{
ofstream mystream(name,ios::out|ios::nocreate);
if (ofstream.bad()) {
//...
}
//...
}
Также можно открыть файл сразу на чтение и запись:
fstream dictionary("concordance", ios::in|ios::out);
Все операции, допустимые для ostream и ostream, можно применять к
fstream. На самом деле, класс fstream является производным от iostream,
который является, в свою очередь, производным от istream и ostream.
Причина, по которой информация по буферизации и форматированию для
ostream и istream находится в виртуальном базовом классе ios, в том,
чтобы заставить действовать всю эту последовательность производных
классов. По этой же причине операции позиционирования в istream и
ostream имеют разные имена - seekp() и seekg(). В iostream есть
отдельные позиции для чтения и записи.
10.5.1 Закрытие потоков
Файл может быть закрыт явно, если вызвать close() для его потока:
mystream.close();
Но это неявно делает деструктор потока, так что явный вызов close()
может понадобиться, если только файл нужно закрыть до достижения
конца области определенности потока.
Здесь возникает вопрос, как реализация может обеспечить
создание предопределенных потоков cout, cin и cerr до их первого
использования и закрытие их только после последнего использования.
Конечно, разные реализации библиотеки потоков из <iostream.h> могут
по-разному решать эту задачу. В конце концов, решение - это
прерогатива реализации, и оно должно быть скрыто от пользователя. Здесь
приводится только один способ, примененный только в одной реализации,
но он достаточно общий, чтобы гарантировать правильный порядок
создания и уничтожения глобальных объектов различных типов.
Основная идея в том, чтобы определить вспомогательный класс,
который по сути служит счетчиком, следящим за тем, сколько раз
<iostream.h> был включен в раздельно компилировавшиеся программные