Бьерн Страуструп (947334), страница 59
Текст из файла (страница 59)
файлы:
class Io_init {
static int count;
//...
public:
Io_init();
^Io_init();
};
static Io_init io_init ;
Для каждого программного файла определен свой объект с именем io_init.
Конструктор для объектов io_init использует Io_init::count как первый
признак того, что действительная инициализация глобальных объектов
потоковой библиотеки ввода-вывода сделана в точности один раз:
Io_init::Io_init()
{
if (count++ == 0) {
// инициализировать cout
// инициализировать cerr
// инициализировать cin
// и т.д.
}
}
Обратно, деструктор для объектов io_init использует Io_count, как
последнее указание на то, что все потоки закрыты:
Io_init::^Io_init()
{
if (--count == 0) {
// очистить cout (сброс, и т.д.)
// очистить cerr (сброс, и т.д.)
// очистить cin
// и т.д.
}
}
Это общий прием работы с библиотеками, требующими инициализации и
удаления глобальных объектов. Впервые в С++ его применил Д. Шварц.
В системах, где при выполнении все программы размещаются в основной
памяти, для этого приема нет помех. Если это не так, то накладные
расходы, связанные с вызовом в память каждого программного файла
для выполнения функций инициализации, будут заметны. Как всегда,
лучше, по возможности, избегать глобальных объектов. Для классов,
в которых каждая операция значительна по объему выполняемой работы,
чтобы гарантировать инициализацию, было бы разумно проверять такие
первые признаки (наподобие Io_init::count) при каждой операции.
Однако, для потоков такой подход был бы излишне расточительным.
10.5.2 Строковые потоки
Как было показано, поток может быть привязан к файлу, т.е. массиву
символов, хранящемуся не в основной памяти, а, например, на диске. Точно
так же поток можно привязать к массиву символов в основной памяти.
Например, можно воспользоваться выходным строковым потоком ostrstream
для форматирования сообщений, не подлежащих немедленной печати:
char* p = new char[message_size];
ostrstream ost(p,message_size);
do_something(arguments,ost);
display(p);
С помощью стандартных операций вывода функция do_something может писать
в поток ost, передавать ost подчиняющимся ей функциям и т.п. Контроль
переполнения не нужен, поскольку ost знает свой размер и при заполнении
перейдет в состояние, определяемое fail(). Затем функция display может
послать сообщение в "настоящий" выходной поток. Такой прием наиболее
подходит в тех случаях, когда окончательная операция вывода
предназначена для записи на более сложное устройство, чем традиционное,
ориентированное на последовательность строк, выводное устройство.
Например, текст из ost может быть помещен в фиксированную область на экране.
Аналогично, istrstream является вводным строковым потоком,
читающим из последовательности символов, заканчивающейся нулем:
void word_per_line(char v[], int sz)
/*
печатать "v" размером "sz" по одному слову в строке
*/
{
istrstream ist(v,sz); // создать istream для v
char b2[MAX]; // длиннее самого длинного слова
while (ist>>b2) cout <<b2 << "\n";
}
Завершающий нуль считается концом файла.
Строковые потоки описаны в файле <strstream.h>.
10.5.3 Буферизация
Все операции ввода-вывода были определены без всякой связи с типом
файла, но нельзя одинаково работать со всеми устройствами без учета
алгоритма буферизации. Очевидно, что потоку ostream, привязанному к
строке символов, нужен не такой буфер, как ostream, привязанному к
файлу. Такие вопросы решаются созданием во время инициализации разных
буферов для потоков разных типов. Но существует только один набор
операций над этими типами буферов, поэтому в ostream нет функций, код
которых учитывает различие буферов. Однако, функции, следящие за
переполнением и обращением к пустому буферу, являются виртуальными.
Это хороший пример применения виртуальных функций для единообразной
работы с эквивалентными логически, но различно реализованными
структурами, и они вполне справляются с требуемыми алгоритмами буферизации.
Описание буфера потока в файле <iostream.h> может выглядеть следующим
образом:
class streambuf { // управление буфером потока
protected:
char* base; // начало буфера
char* pptr; // следующий свободный байт
char* gptr; // следующий заполненный байт
char* eptr; // один из указателей на конец буфера
char alloc; // буфер, размещенный с помощью "new"
//...
// Опустошить буфер:
// Вернуть EOF при ошибке, 0 - удача
virtual int overflow(int c = EOF);
// Заполнить буфер:
// Вернуть EOF в случае ошибки или конца входного потока,
// иначе вернуть очередной символ
virtual int underflow();
//...
public:
streambuf();
streambuf(char* p, int l);
virtual ~streambuf();
int snextc() // получить очередной символ
{
return (++gptr==pptr) ? underflow() : *gptr&0377;
}
int allocate(); // отвести память под буфер
//...
};
Подробности реализации класса streambuf приведены здесь только для
полноты представления. Не предполагается, что есть общедоступные
реализации, использующие именно эти имена. Обратите внимание на
определенные здесь указатели, управляющие буфером; с их помощью
простые посимвольные операции с потоком можно определить максимально
эффективно (и причем однократно) как функции-подстановки. Только
функции overflow() и underflow() требует своей реализации для каждого
алгоритма буферизации, например:
class filebuf : public streambuf {
protected:
int fd; // дескриптор файла
char opened; // признак открытия файла
public:
filebuf() { opened = 0; }
filebuf(int nfd, char* p, int l)
: streambuf(p,l) { /* ... */ }
~filebuf() { close(); }
int overflow(int c=EOF);
int underflow();
filebuf* open(char *name, ios::open_mode om);
int close() { /* ... */ }
//...
};
int filebuf::underflow() // заполнить буфер из "fd"
{
if (!opened || allocate()==EOF) return EOF;
int count = read(fd, base, eptr-base);
if (count < 1) return EOF;
gptr = base;
pptr = base + count;
return *gptr & 0377; // &0377 предотвращает размножение знака
}
За дальнейшими подробностями обратитесь к руководству по реализации
класса streambuf.
10.6 Ввод-вывод в С
Поскольку текст программ на С и на С++ часто путают, то путают иногда
и потоковый ввод-вывод С++ и функции ввода-вывода семейства printf для
языка С. Далее, т.к. С-функции можно вызывать из программы на С++, то
многие предпочитают использовать более знакомые функции ввода-вывода С.
По этой причине здесь будет дана основа функций ввода-вывода С.
Обычно операции ввода-вывода на С и на С++ могут идти по очереди на
уровне строк. Перемешивание их на уровне посимвольного ввода-вывода
возможно для некоторых реализаций, но такая программа может быть
непереносимой. Некоторые реализации потоковой библиотеки С++ при допущении
ввода-вывода на С требуют вызова статической функции-члена
ios::sync_with_stdio().
В общем, потоковые функции вывода имеют перед стандартной
функцией С printf() то преимущество, что потоковые функции обладают
определенной типовой надежностью и единообразно определяют вывод
объектов предопределенного и пользовательского типов.
Основная функция вывода С есть
int printf(const char* format, ...)
и она выводит произвольную последовательность параметров в формате,
задаваемом строкой форматирования format. Строка форматирования состоит
из объектов двух типов: простые символы, которые просто копируются в
выходной поток, и спецификации преобразований, каждая из которых
преобразует и печатает очередной параметр. Каждая спецификация
преобразования начинается с символа %, например
printf("there were %d members present.",no_of_members);
Здесь %d указывает, что no_of_members следует считать целым и печатать
как соответствующую последовательность десятичных цифр. Если
no_of_members==127, то будет напечатано
there were 127 members present.
Набор спецификаций преобразований достаточно большой и обеспечивает
большую гибкость печати. За символом % может следовать:
- необязательный знак минус, задающий выравнивание влево в указанном
поле для преобразованного значения;
d необязательная строка цифр, задающая ширину поля; если в
преобразованном значении меньше символов, чем ширина строки, то оно
дополнится до ширины поля пробелами слева (или справа, если дана
спецификация выравнивания влево); если строка ширины поля начинается
с нуля, то дополнение будет проводится нулями, а не пробелами;
. необязательный символ точка служит для отделения ширины поля от
последующей строки цифр;
d необязательная строка цифр, задающая точность, которая определяет
число цифр после десятичной точки для значений в спецификациях
e или f, или же задает максимальное число печатаемых символов
строки;
* для задания ширины поля или точности может использоваться * вместо
строки цифр. В этом случае должен быть параметр целого типа, который
содержит значение ширины поля или точности;
h необязательный символ h указывает, что последующая спецификация d,
o, x или u относится к параметру типа короткое целое;
l необязательный символ l указывает, что последующая спецификация d,
o, x или u относится к параметру типа длинное целое;
% обозначает, что нужно напечатать сам символ %; параметр не нужен;
c символ, указывающий тип требуемого преобразования. Символы
преобразования и их смысл следующие:
d Целый параметр выдается в десятичной записи;
o Целый параметр выдается в восьмеричной записи;
x Целый параметр выдается в шестнадцатеричной записи;
f Вещественный или с двойной точностью параметр выдается в
десятичной записи вида [-]ddd.ddd, где число цифр после
точки равно спецификации точности для параметра. Если точность
не задана, печатается шесть цифр; если явно задана точность 0,
точка и цифры после нее не печатаются;
e Вещественный или с двойной точностью параметр выдается в
десятичной записи вида [-]d.ddde+dd; здесь одна цифра перед
точкой, а число цифр после точки равно спецификации точности
для параметра; если она не задана печатается шесть цифр;
g Вещественный или с двойной точностью параметр печатается по той
спецификации d, f или e, которая дает большую точность при
меньшей ширине поля;
c Символьный параметр печатается. Нулевые символы игнорируются;
s Параметр считается строкой (символьный указатель), и печатаются
символы из строки до нулевого символа или до достижения числа
символов, равного спецификации точности; но, если точность
равна 0 или не указана, печатаются все символы до нулевого;
p Параметр считается указателем и его вид на печати зависит от
реализации;
u Беззнаковый целый параметр печатается в десятичной записи.
Несуществующее поле или поле с шириной, меньшей реальной, приведет
к усечению поля. Дополнение пробелами происходит, если только
спецификация ширины поля больше реальной ширины.
Ниже приведен более сложный пример:
char* src_file_name;
int line;
char* line_format = "\n#line %d \"%s\"\n";
main()
{
line = 13;
src_file_name = "C++/main.c";
printf("int a;\n");
printf(line_format,line,src_file_name);
printf("int b;\n");
}
в котором печатается
int a;
#line 13 "C++/main.c"
int b;
Использование printf() ненадежно в том смысле, что нет никакого