class complex (* double re, im; public: complex(double r = 0, double i = 0) (* re=r; im=i; *)
friend double real(complex amp; a) (* returna.re; *) friend double real(complex amp; a) (* returna.re; *)
friend complex operator+(complex, complex); friend complex operator-(complex, complex); friend complex operator*(complex, complex); friend complex operator/(complex, complex); // ... *);
Операцию «« для нового типа complex можно определить так:
ostream amp; operator««(ostream amp;s, complex z) (* return s «« "(" «« real(z) «« "," «« imag(z) «« ")"; *)
и использовать точно так же, как для встроенного типа:
complex x(1,2); // ... cout «„ "x = " «« x «« «\n“;
получая при этом
x = (1,2)
Определение действия вывода для определяемого пользовтелем типа не требует ни модификации описания класса ostream, ни доступа к структуре данных (скрытой), которую этот класс поддерживает. Очень удачно, что имеет место первое, потому что описание класса ostream находится в стандартных заголвочных файлах, к которым у обычного пользователя нет доступа на запись. Последнее также важно, потому что обеспечивает зщиту от случайной порчи структуры данных. Это также позволяет менять реализацию ostream не влияя на пользовательские прораммы.
8.2.3 Некоторые Подробности Разработки
Операция вывода используется, чтобы избежать той многоловности, которую дало бы использование функции вывода. Но почему ««?
Возможности изобрести новый лексический символ нет (#6.2). Операция присваивания была кандидатом одновременно и на ввод, и на вывод, но оказывается, большинство людей препочитают, чтобы операция ввода отличалась от операции вывода. Кроме того, = не в ту сторону связывается (ассоциируется), то есть 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««b=“ «« (a««b) «« «\n“;
В С++ нет выражений с символьными значениями, в частноти, '\n' является целым (со значением 10, если используется набор символов ASCII), поэтому
cout «« "x = " «« x «« '\n';
напечатает число 10, а не ожидаемый символ новой строки. Эту и аналогичные проблемы можно сгладить, определив несколко макросов (в которых используются стандартные имена симвлов ASСII):
#define sp «„ " " #define ht „« «\t“ #define nl «« «\n“
Теперь предыдущий пример запишется в виде:
cout «« "x = " «« x nl;
Для печати символов предоставляются функции ostream::put (char) и chr(int) (см. #8.2.4). Хотя в некоторых кругах нсинтаксические макросы считаются худшим видом макросов, мне они нравятся.
Рассмотрим примеры:
cout «„ x „« " " «« y «« " " «« z «« «\n“; cout «« "x = " «« x «« ", y = " «« y «« «\n“;
Люди находят их трудно читаемыми из-за большого числа кавычек и того, что операция вывода внешне выглядит слишком непривычно. Здесь могут помочь приведенные выше макросы и несколько отступов:
cout «« x sp «« y sp «« z nl; cout «« "x = " «« x «« ", y = " «« y nl;
8.2.4 Форматированный Вывод
Пока «« применялась только для неформатированного вывда, и на самом деле в реальных программах она именно для этго главным образом и применяется. Помимо этого существует также несколько форматирующих функций, создающих представление своего параметра в виде строки, которая используется для вывода. Их второй (необязательный) параметр указывает, сколко символьных позиций должно использоваться.
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««form() эквивалентно применению стандартной функции вывода языка C printf()*. form() возвращает строку, получамую в результате преобразования и форматирования ее парамеров, которые стоят после первого управляющего параметра – строки формата format. Строка формата состоит из объектов двух типов: обычных символов, которые просто копируются в пток вывода, и спецификаций преобразования, каждая из которых влечет преобразование и печать следующего из параметров. Кадая спецификация преобразования начинается с символа %. Наример:
– * Объяснение того, как применяются строки формата, – это слегка отредактированный вариант спецификации printf(). (прим. автора)
cout«„form(«there were %d members present“,no_of_members);
Здесь %d указывает, что no_of_members должно рассматрваться как int и печататься в виде соответствующей последовтельности десятичных цифр. Если no_of_members==127, вывод бдет такой:
there were 127 members present
Множество спецификаций преобразования довольно велико и обеспечивает высокую степень гибкости. После % может стоять:
– необязательный знак минус, который задает выравнивание преобразованного значения влево в указанном поле;
d необязательная строка цифр, задающая ширину поля. Если преобразованное значение имеет меньше цифр, чем ширина поля, оно будет дополняться пробелами слева (или справа, если был задан индикатор выравнивания влево) до заполнния всей ширины поля; если ширина поля начинается с нля, то вместо дополнения пробелами будет делаться допонение нулями;
. необязательная точка, для отделения ширины поля от
следующей строки цифр;
d необязательная строка цифр, специфицирующая точность, которая задает число цифр после десятичной точки для преобразований e и f или печатаемых символов для строки;
* в ширине поля или точности вместо строки цифр может стять *. В этом случае ширина поля и точность задается цлым параметром;
h необязательный символ h; указывает на то, что идущие за ним d, o, x или y соответствуют параметру короткое цлое;
l необязательный символ h; указывает на то, что идущие за ним d, o, x или y соответствуют параметру длинное целое;
% указывает, что должен быть напечатан символ %, никакие параметры при этом не затрагиваются;
c символ, указывающий, какой тип преобразования должен применяться. Символы преобразования и их значения таквы:
d целый параметр преобразуется в десятичную запись;
o целый параметр преобразуется в восьмеричную запись;
x целый параметр преобразуется в шестнадцатиричную запись;
f параметр float или double преобразуется в десятичную запись вида [-]ddd.ddd, где число, задаваемое цифрами d после десятичной точки, эквивалентно спецификации тоности для параметра. Если точность опущена, дается шесть цифр; если точность явно задана как 0, то не печатается десятичная точка и не печатается ни одной цифры;
e параметр float или double преобразуется в десятичную запись вида [-]d.ddde+dd, где перед десятичной точкой стоит одна цифра, а число, задаваемое цифрами после дсятичной точки, эквивалентно спецификации точности для параметра; когда точность опущена, выдается шесть цифр;
g параметр float или double печатается в том из видов d, f или e, который обеспечивает полную точность при минмальной затрате места;
c печатается символьный параметр, пустые символы игнорруются;
s параметр воспринимается как строка (указатель на сивол), и печатаются символы из строки до пустого символа или до тех пор, пока не будет достигнуто число символов, указанное спецификацией точности; но если точность равна нулю, печатаются все символы до пустого;
u беззнаковый целый параметр преобразуется в десятичную запись.
Несуществующая или недостаточная ширина поля никогда не приводит к обрезанию поля; дополнение поля записи имеет место только в том случае, если указанная ширина поля превышает фактическую ширину.
Вот более сложный пример:
char* src_file_name;
int line; char* line_format = «\n#line %d \»%s\"\n"; //... cout «„ „int a;\n“; cout «« form(line_format,line,src_file_name); cout «« «int b;\n“;
который печатает
int a;
#line 13 «С++/main.c» int b;
Применение form() небезопасно в смысле того, что не вполняется проверка типа. Вот, например, хорошо хорошо извесный способ получить непредсказуемый вывод и/или дамп (core dump):
char x; // ... cout«„form(«bad input char: %s“,x);
Правда, она дает большую гибкость в том виде, который хорошо знаком программистам на C. Потоковый вывод можно смшивать с выводом в стиле printf().
В настоящее время нет полностью удовлетворительных средств, обеспечивающих форматированный вывод типов, опредляемых пользователем* В частности, вероятно нужно будет найти стандартный способ передавать функции вывода для определяемго пользователем типа информацию, которая позволит ей опредлить пространственные ограничения, вид заполнения, левое или правое выравнивание и т.п. такими, какими они определяются в ее вызове. Вполне осуществимый, но не идеальный подход состит в том, чтобы снабжать определяемый пользователем тип фунциями, которые порождают соответствующее строковое предсталение объекта, для которого они вызываются, аналогично форматирующим функциям oct(), hex() и т.д. Например:
class complex (* float re,im; public: // ... char* string(char* format) (* return form(format,re,im); *) *); // ... cout «„ z.string(«(%.3f,%.3f)“);
Память для хранения строк, которые возвращают form(), hex() и т.п., берется из одного статически размещаемого цилического буфера, поэтому не имеет смысла сохранять указтель, возвращаемый любой из этих функций, для дальнейшего ипользования. Указываемые символы будут меняться.
8.2.5 Виртуальная Функция Вывода
Иногда функция вывода должна быть virtual. Рассмотрим пример класса shape, который дает понятие фигуры (#1.18):
class shape (* // ... public: // ... virtual void draw(ostream amp; s); // рисует «this» на "s" *);
class circle : public shape (* int radius; public: // ... void draw(ostream amp;); *);
То есть, круг имеет все признаки фигуры и может обрабтываться как фигура, но имеет также и некоторые специальные свойства, которые должны учитываться при его обработке.
Чтобы поддерживать для таких классов стандартную пардигму вывода, операция «« определяется так:
ostream amp; operator«„(ostream amp; s, shape* p) (* p-“draw(s); return s; *)
Если next – итератор типа определенного в #7.3.3, то список фигур распечатывается например так:
while ( p = next() ) cout «« p;
8.3 Файлы и Потоки
Потоки обычно связаны с файлами. Библиотека потоков содает стандартный поток ввода cin, стандартный поток вывода cout и стандартный поток ошибок cerr. Программист может отрывать другие файлы и создавать для них потоки.
8.3.1 Инициализация Потоков Вывода
ostream имеет конструкторы:
class ostream (* // ... ostream(streambuf* s); // связывает с буфером потока ostream(int fd); // связывание для файла ostream(int size, char* p); // связывает с вектором *);
Главная работа этих конструкторов – связывать с потоком буфер. streambuf – класс, управляющий буферами; он описываеся в #8.6, как и класс filebuf, управляющий streambuf для файла. Класс filebuf является производным от класса streambuf.
Описание стандартных потоков вывода cout и cerr, которое находится в исходных кодах библиотеки потоков ввода/вывода, выглядит так:
// описать подходящее пространство буфера char cout_buf[BUFSIZE]
// сделать «filebuf» для управления этим пространством //связать его с UNIX'овским потоком вывода 1 (уже открытым) filebuf cout_file(1,cout_buf,BUFSIZE);
// сделать ostream, обеспечивая пользовательский интерфейс ostream cout( amp;cout_file);
char cerr_buf[1];
// длина 0, то есть, небуферизованный
// UNIX'овский поток вывода 2 (уже открытый) filebuf cerr_file(2,cerr_buf,0);
ostream cerr( amp;cerr_file);
Примеры двух других конструкторов ostream можно найти в #8.3.3 и #8.5.
8.3.2 Закрытие Потоков Вывода
Деструктор для ostream сбрасывает буфер с помощью откртого члена функции ostream::flush():
ostream::~ostream() (* flush(); // сброс *)
Сбросить буфер можно также и явно. Например:
cout.flush();
8.3.3 Открытие Файлов
Точные детали того, как открываются и закрываются файлы, различаются в разных операционных системах и здесь подробно не описываются. Поскольку после включения «stream.h» станвятся доступны cin, cout и cerr, во многих (если не во всех) программах не нужно держать код для открытия файлов. Вот, онако, программа, которая открывает два файла, заданные как параметры командной строки, и копирует первый во второй:
#include «stream.h»
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( amp;f1);
filebuf f2; if (f2.open(argv[2],output) == 0) error(«не могу создать выходной файл»,argv[2]); ostream to( amp;f2);
char ch; while (from.get(ch)) to.put(ch);
if (!from.eof() !! to.bad()) error(«случилось нечто странное»,""); *)
Последовательность действий при создании ostream для именованного файла та же, что используется для стандартных потоков: (1) сначала создается буфер (здесь это делается поредством описания filebuf); (2) затем к нему подсоединяется файл (здесь это делается посредством открытия файла с помощью функции filebuf::open()); и, накрнец, (3) создается сам
ostream с filebuf в качестве параметра. Потоки ввода обрабтываются аналогично.
Файл может открываться в одной из двух мод:
enum open_mode (* input, output *);
Действие filebuf::open() возвращает 0, если не может окрыть файл в соответствие с требованием. Если пользователь пытается открыть файл, которого не существует для output, он будет создан.
Перед завершением программа проверяет, находятся ли птоки в приемлемом состоянии (см. #8.4.2). При завершении программы открытые файлы неявно закрываются.
Файл можно также открыть одновременно для чтения и запси, но в тех случаях, когда это оказывается необходимо, пардигма потоков редко оказывается идеальной. Часто лучше расматривать такой файл как вектор (гигантских размеров). Можно определить тип, который позволяет программе обрабатывать файл как вектор, см. Упражнения 8– 10.
8.3.4 Копирование Потоков
Есть возможность копировать потоки. Например:
cout = cerr;
В результате этого получаются две переменные, ссылающися на один и тот же поток. Гавным образом это бывает полезно для того, чтобы сделать стандартное имя вроде cin ссылающимся на что-то другое (пример этого см. в #3.1.6)
8.4 Ввод
Ввод аналогичен выводу. Имеется класс istream, который предоставляет операцию »» («взять из») для небольшого мнжества стандартных типов. Функция operator»» может опредляться для типа, определяемого пользователем.
8.4.1 Ввод Встроенных Типов
Класс istream определяется так:
class istream (* // ... public: istream amp; operator»»(char*); // строка istream amp; operator»»(char amp;); // символ istream amp; operator»»(short amp;); istream amp; operator»»(int amp;); istream amp; operator»»(long amp;); istream amp; operator»»(float amp;); istream amp; operator»»(double amp;); // ... *);
Функции ввода определяются в таком духе:
istream amp; istream::operator»»(char amp; c); (* // пропускает пропуски int a; // неким образом читает символ в "a" c = a; *)
Пропуск определяется как стандартнчй пропуск в C, через вызов isspase() в том виде, как она определена в «ctype.h» (пробел, табуляция, символ новой строки, перевод формата и возврат каретки).
В качестве альтернативы можно использовать функции get():
class istream (* // ... istream amp; get(char amp; c); // char istream amp; get(char* p, int n, int ='\n'); // строка *);
Они обрабатывают символы пропуска так же, как остальные символы. Функция istream::get(char) читает один символ в свой параметр; другая istream::get читает не более n символов в вектор символов, начинающийся в p. Необязательный третий праметр используется для задания символа остановки (иначе, терминатора или ограничителя), то есть этот символ читаться не будет. Если будет встречен символ ограничитель, он остнется как первый символ потока. По умолчанию вторая функция get будет читать самое большее n символов, но не больше чем одну строку, '\n' является ограничителем по умолчанию. Необзательный третий параметр задает символ, который читаться не будет. Например:
cin.get(buf,256,'\t');
будет читать в buf не более 256 символов, а если встртится табуляция ('\t'), то это приведет к возврату из get. В этом случае следующим символом, который будет считан из cin, будет '\t'.
Стандартный заголовочный файл «ctype.h» определяет неколько функций, которые могут оказаться полезными при осществлении ввода:
int isalpha(char) // 'a'..'z' 'A'..'Z' int isupper(char) // 'A'..'Z' int islower(char) // 'a'..'z' int isdigit(char) // '0'..'9' int isxdigit(char) // '0'..'9' 'a'..'f' 'A'..'F' int isspase(char) // ' ' '\t' возврат новая строка // перевод формата int iscntrl(char) // управляющий символ // (ASCII 0..31 и 127) int ispunct(char) // пунктуация:ни один из вышеперечисленных int isalnum(char) // isalpha() ! isdigit() int isprint(char) // печатаемый: ascii ' '..'-' int isgraph(char) // isalpha() ! isdigit() ! ispunct() int isascii(char c) (* return 0«=c amp; amp; c«=127; *)
Все кроме isascii() реализуются внешне одинаково, с прменением символа в качестве индекса в таблице атрибутов символов. Поэтому такие выражения, как
(('a'«=c amp; amp; c«='z') !! ('A'«=c amp; amp; c«='Z')) // алфавитный
не только утомительно пишутся и чреваты ошибками (на мшине с набором символов EBCDIC оно будет принимать неалфавиные символы), они также и менее эффективны, чем применение стандартной функции:
isalpha(c)
8.4.2 Состояния Потока
Каждый поток (istream или ostream) имеет ассоциированное с ним состояние, и обработка ошибок и нестандартных условий осуществляется с помощью соответствующей установки и проверки этого состояния.
Поток может находиться в одном из следующих состояний:
enum stream_state (* _good, _eof, _fail, _bad *);
Если состояние _good или _eof, значит последняя операция ввода прошла успешно. Если состояние _good, то следующая опрация ввода может пройти успешно, в противном случае она зкончится неудачей. Другими словами, применение операции ввода к потоку, который не находится в состоянии _good, является пустой операцией. Если делается попытка читать в переменную v, и операция оканчивается неудачей, значение v должно отаться неизменным (оно будет неизменным, если v имеет один из тех типов, которые обрабатываются функциями членами istream или ostream). Отличие между состояниями _fail и _bad очень незначительно и предсавляет интерес только для разработчиков операций ввода. В состоянии _fail предполагается, что поток не испорчен и никакие символы не потеряны. В состоянии _bad может быть все что угодно.
Состояние потока можно проверять например так:
switch (cin.rdstate()) (* case _good: // последняя операция над cin прошла успешно break; case _eof: // конец файла break; case _fail: // некоего рода ошибка форматирования // возможно, не слишком плохая break; case _bad: // возможно, символы cin потеряны break; *)
Для любой переменной z типа, для которого определены операции «„ и “», копирующий цикл можно написать так:
while (cin»»z) cout «„ z «« «\n“;
Например, если z – вектор символов, этот цикл будет брать стандартный ввод и помещать его в стандартный вывод по одному слову (то есть, последовательности символов без пробла) на строку.
Когда в качестве условия используется поток, происходит проверка состояния потока, и эта проверка проходит успешно (то есть, значение условия не ноль) только если состояние _good. В частности, в предыдущем цикле проверялось состояние istream, которое возвращает cin»»z. Чтобы обнаружить, почему цикл или проверка закончились неудачно, можно исследовать состояние. Такая проверка потока реализуется операцией преоразования (#6.3.2).
Делать проверку на наличие ошибок после каждого ввода или вывода действительно не очень удобно, и обычно источником ошибок служит программист, не сделавший этого в том месте, где это существенно. Например, операции вывода обычно не проверяются, но они могут случайно не сработать. Парадигма потка ввода/вывода построена так, чтобы когда в С++ появится (если это произойдет) механизм обработки исключительных ситаций (как средство языка или как стандартная библиотека), его будет легко применить для упрощения и стандартизации обрабоки ошибок в потоках ввода/вывода.
8.4.3 Ввод Типов, Определяемых Пользователем
Ввод для пользовательского типа может определяться точно так же, как вывод, за тем исключением, что для операции ввода важно, чтобы второй параметр был ссылочного типа. Например:
istream amp; operator»»(istream amp; s, complex amp; a) /* форматы ввода для complex; "f" обозначает float: f ( f ) ( f , f ) */ (* double re = 0, im = 0; char c = 0;
s »» c; if (c == '(') (* s »» re »» c; if (c == ',') s »» im »» c; if (c != ')') s.clear(_bad); // установить state *) else (* s.putback(c); s »» re; *)
if (s) a = complex(re,im); return s; *)
Несмотря на то, что не хватает кода обработки ошибок, большую часть видов ошибок это на самом деле обрабатывать бдет. Локальная переменная c инициализируется, чтобы ее значние не оказалось случайно '(' после того, как операция окочится неудачно. Завершающая проверка состояния потока гарантирует, что значение параметра a будет изменяться только в том случае, если все идет хорошо.
Операция установки состояния названа clear() (очистить), потому что она чаще всего используется для установки состония потока заново как _good. _good является значением парметра по умолчанию и для istream::clear(), и для ostream::clear().
Над операциями ввода надо поработать еще. Было бы, в частности, замечательно, если бы можно было задавать ввод в терминах образца (как в языках Snobol и Icon), а потом проврять, прошла ли успешно вся операция ввода. Такие операции должны были бы, конечно, обеспечивать некоторую дополнителную буферизацию, чтобы они могли воссанавливать поток ввода в его исходное состояние после неудачной попытки распознавания.
8.4.4 Инициализация Потоков Ввода
Естественно, тип istream, так же как и ostream, снабжен конструктором:
class istream (*
// ... istream(streambuf* s, int sk =1, ostream* t =0); istream(int size, char* p, int sk =1); istream(int fd, int sk =1, ostream* t =0); *);
Параметр sk задает, должны пропускаться пропуски или нет. Параметр t (необязательный) задает указатель на ostream, к которому прикреплен istream. Например, cin прикреплен к cout; это значит, что перед тем, как попытаться читать симвлы из своего файла, cin выполняет
cout.flush(); // пишет буфер вывода
С помощью функции istream::tie() можно прикрепить (или открепить, с помощью tie(0)) любой ostream к любому istream. Например:
int y_or_n(ostream amp; to, istream amp; from) /* «to», получает отклик из «from» */ (* ostream* old = from.tie( amp;to); for (;;) (* cout «« "наберите Y или N: "; char ch = 0; if (!cin.get(ch)) return 0;
if (ch != '\n') (* // пропускает остаток строки char ch2 = 0; while (cin.get(ch2) amp; amp; ch2 != '\n') ; *) switch (ch) (* case 'Y': case 'y': case '\n': from.tie(old); // восстанавливает старый tie return 1; case 'N': case 'n': from.tie(old); // восстанавливает старый tie return 0; default: cout «« "извините, попробуйте еще раз: "; *) *) *)
Когда используется буферизованный ввод (как это происхдит по умолчанию), пользователь не может набрав только одну букву ожидать отклика. Система ждет появления символа новой строки. y_or_n() смотрит на первый символ строки, а остальные игноирует.
Символ можно вернуть в поток с помощью функции istream:: putback(char). Это позволяет программе «заглядывать вперед» в поток ввода.
8.5 Работа со Строками
Можно осуществлять действия, подобные вводу/выводу, над символьным вектором, прикрепляя к нему istream или ostream. Например, если вектор содержит обычную строку, завершающуюся нулем, для печати слов из этого вектора можно использовать приведенный выше копирующий цикл:
void word_per_line(char v[], int sz) /* печатет "v" размера «sz» по одному слову на строке */ (* istream ist(sz,v); // сделать istream для v char b2[MAX]; // больше наибольшего слова while (ist»»b2) cout «„ b2 «« «\n“; *)
Завершающий нулевой символ в этом случае интерпретируеся как символ конца файла.
В помощью ostream можно отформатировать сообщения, котрые не нужно печатать тотчас же:
char* p = new char[message_size]; ostream ost(message_size,p); do_something(arguments,ost); display(p);
Такая операция, как do_something, может писать в поток ost, передавать ost своим подоперациям и т.д. с помощью стадартных операций вывода. Нет необходимости делать проверку на переполнение, поскольку ost знает свою длину и когда он будет переполняться, он будет переходить в состояние _fail. И, нконец, display может писать сообщения в «настоящий» поток ввода. Этот метод может оказаться наиболее полезным, чтобы справляться с ситуациями, в которых окончательное отображение данных включает в себя нечто более сложное, чем работу с трдиционным построчным устройством вывода. Например, текст из ost мог бы помещаться в располагающуюся где-то на экране оласть фиксированного размера.
8.6 Буферизация
При задании операций ввода/вывода мы никак не касались типов файлов, но ведь не все устройства можно рассматривать одинаково с точки зрения стратегии буферизации. Например, для ostream, подключенного к символьной строке, требуется буферзация другого вида, нежели для ostream, подключенного к фалу. С этими пробемами можно справиться, задавая различные бферные типы для разных потоков в момент инициализации (обратите внимание на три конструктора класса ostream). Есть только один набор операций над этими буферными типами, поэтму в функциях ostream нет кода, их различающего. Однако фунции, которые обрабатывают переполнение сверху и снизу, виртальные. Этого достаточно, чтобы справляться с необходимой в данное время стратегией буферизации. Это также служит хорошим примером применения виртуальных функций для того, чтобы сдлать возможной однородную обработку логически эквивалентных средств с различной реализацией. Описание буфера потока в «stream.h» выглядит так:
struct streambuf (* // управление буфером потока
char* base; // начало буфера char* pptr; // следующий свободный char char* qptr; // следующий заполненный char char* eptr; // один из концов буфера char alloc; // буфер, выделенный с помощью new
// Опустошает буфер: // Возвращает EOF при ошибке и 0 в случае успеха virtual int overflow(int c =EOF);
// Заполняет буфер
// Возвращет EOF при ошибке или конце ввода, // иначе следующий char virtual int underflow();
int snextc() // берет следующий char (* return (++qptr==pptr) ? underflow() : *qptr amp;0377; *)
// ...
int allocate() //выделяет некоторое пространство буфера
streambuf() (* /* ... */*) streambuf(char* p, int l) (* /* ... */*) ~streambuf() (* /* ... */*) *);
Обратите внимание, что здесь определяются указатели, нобходимые для работы с буфером, поэтому обычные посимвольные действия можно определить (только один раз) в виде максимално эффективных inlinфункций. Для каждой конкретной стратгии буферизации необходимо определять только функции перепонения overflow() и underflow(). Например:
struct filebuf : public streambuf (*
int fd; // дескриптор файла char opened; // файл открыт
int overflow(int c =EOF); int underflow();
// ...
// Открывает файл: // если не срабатывает, то возвращет 0, // в случае успеха возвращает «this» filebuf* open(char *name, open_mode om); int close() (* /* ... */ *)
filebuf() (* opened = 0; *) filebuf(int nfd) (* /* ... */ *) filebuf(int nfd, char* p, int l) : (p,l) (* /*...*/ *) ~filebuf() (* close(); *) *);
int filebuf::underflow() // заполняет буфер из fd (* if (!opened !! allocate()==EOF) return EOF;
int count = read(fd, base, eptr-base); if (count « 1) return EOF;
qptr = base; pptr = base + count; return *qptr amp; 0377; *)
8.7 Эффективность