#include «screen. h» #include «stream. h»

enum color (* black='*', white=' ' *);

char screen[XMAX][YNAX];

void screen_init() (* for (int y=0; y«YMAX; y++) for (int x=0; x«XMAX; x++) screen[x][y] = white; *)

Точки печатаются, только если они есть на экране:

inline int on_screen(int a, int b) (* return 0«=a amp; amp; a«XMAX amp; amp; 0«=b amp; amp; b«YMAX; *)

void put_point(int a, int b) (* if (on_screen(a, b)) screen[a][b] = black; *)

Для рисования линий используется функция put_line():

void put_line(int x0, int y0, int x1, int y1) /* Строит линию от (x0,y0) до (x1,y1). Строится линия b(x-x0) + a(y-y0) = 0. Минимизирует abs(eps), где eps = 2*(b(x-x0)+ a(y-y0)). См. Newman and Sproull: ``Principles of Interactive Computer Graphics'' McGraw-Hill, New York, 1979, pp 33-44. */ (* register dx = 1; int a = x1 – x0; if (a « 0) dx = -1, a = - a; register dy = 1; int b = y1 – y0;

if (b « 0) dy = -1, b = - b; int two_a = 2*a; int two_b = 2*b; int xcrit = - b + two_a; register eps = 0; for (;;) (* put_point(x0,y0); if(x0==x1 amp; amp; y0==y1) break; if(eps „= xcrit) x0 += dx, eps += two_b; if(eps“=a!! a«=b) y0 += dy, eps -= two_a; *) *)

Предоставляются функции для очистки экрана и его обноления:

void screen_clear() (* screen_init(); *) // очистка

void screen_refresh() // обновление (* for (int y=YMAX-1; 0«=y; y–) (* // сверху вниз for (int x=0; x«XMAX; x++) // слева направо cout. put(screen[x][y]); cout. put('\n'); *) *)

Функция ostream::put() применяется для печати символов как символов; ostream::operator««() печатает символы как млые целые. Пока что вы может представлять себе, что эти опрделения доступны только в откомпилированном виде, который вы изменить не можете.

НЕ нашли? Не то? Что вы ищете?

7.6.2 Библиотека Фигур

Нам нужно определить общее понятие фигуры (shape). Это надо сделать таким образом, чтобы оно использовалось (как бзовый класс) всеми конкретными фигурами (например, кругами и квадратами), и так, чтобы любой фигурой можно было манипулровать исключительно через интерфейс, предоставляемый классом shape:

struct shape (* shape() (* shape_list. append(this); *)

virtual point north() (*return point(0,0);*) // север virtual point south() (*return point(0,0);*) // юг virtual point east() (*return point(0,0);*) // восток virtual point neast() (*return point(0,0);*)//северо-восток virtual point seast() (*return point(0,0);*) // юго-восток

virtual void draw() (**); // нарисовать virtual void move(int, int) (**); // переместить *);

Идея состоит в том, что расположение фигуры задается с помощью move(), и фигура помещается на экран с помощью draw(). Фигуры можно располагать относительно друг друга, ипользуя понятие точки соприкосновения, и эти точки перечислются после точек компаса (сторон света). Каждая конкретная фигура определяет свой смысл этих точек, и каждая определяет способ, которым она рисуется. Для экономии места здесь на смом деле определяются только необходимые в этом примере строны света. Конструктор shape::shape() добавляет фигуру в список фигур shape_list. Этот список является gslist, то есть, одним из вариантов обобщенного односвязанного списка, определенного в #7.3.5. Он и соответствующий итератор были

сделаны так:

typedef shape* sp; declare(gslist, sp);

typedef gslist(sp) shape_lst; typedef gslist_iterator(sp) sp_iterator;

поэтому shape_list можно описать так:

shape_lst shape_list;

Линию можно построить либо по двум точкам, либо по точке и целому. В последнем случае создается горизонтальная линия, длину которой определяет целое. Знак целого указывает, каким концом является точка: левым или правым. Вот определение:

class line : public shape (* /* линия из 'w' в 'e' north() определяется как ``выше центра и на север как до самой северной точки'' */ point w, e; public: point north() (* return point((w. x+e. x)/2,e. y«w. y?w. y:e. y); *)

point south() (* return point((w. x+e. x)/2,e. y«w. y?e. y:w. y); *)

void move(int a, int b) (* w. x += a; w. y += b; e. x += a; e. x += b; *) void draw() (* put_line(w, e); *)

line(point a, point b) (* w = a; e = b; *) line(point a, int l) (* w = point(a. x+l-1,a. y); e = a; *) *);

Аналогично определяется прямоугольник rectangle:

class rectangle : public shape (* /* nw – n – ne! ! ! ! w c e! ! ! ! sw – s – se */ point sw, ne; public: point north() (* return point((sw. x+ne. x)/2,ne. y); *) point south() (* return point((sw. x+ne. x)/2,sw. y); *) point neast() (* return ne; *) point swest() (* return sw; *) void move (int a, int b) (* sw. x+=a; sw. y+=b; ne. x+=a; ne. y+=b; *) void draw(); rectangle(point, point); *);

Прямоугольник строится по двум точкам. Код усложняется из-за необходимости выяснять относительное положение этих тчек:

rectangle::rectangle(point a, point b); (* if (a. x «= b. x) (* (* sw = a; ne = b; *) else (* sw = point(a. x,b. y); ne = point(b. x,a. y); *) *) else (* if (a. y «= b. y) (* sw = point(b. x,a. y); ne = point(a. x,b. y); *) else (* sw = b; ne = a; *) *) *)

Чтобы построить прямоугольник, строятся четыре его строны:

void rectangle::draw(); (* point nw(sw. x,ne. y); point se(ne. x,sw. y); put_line(nw, ne); put_line(ne, se); put_line(se, sw); put_line(sw, nw); *)

Помимо определений фигур в библиотеке фигур содержатся функции для работы с ними. Например:

void shape_refresh(); // рисует все фигуры void stack(shape* p, shape* q); // ставит p на верх q

Чтобы справиться с нашим наивным экраном, нужна обновлющая функция. Она просто рисует все фигуры заново. Обратите внимание, что она совершенно не представляет, какие фигуры рисует:

void shape_refresh() (* screen_clear(); sl_iterator next(shape_list); shape* p; while ( p=next() ) p-»draw(); screen_refresh(); *)

И вот, наконец, настоящая сервисная функция (утилита). Она кладет одну фигуру на верх другой, задавая, что south() одной должен быть сразу над north() другой:

void stack(shape* q, shape* p) // ставит p на верх q (* point n = p-»north(); point s = q-»south(); q-»move(n. x-s. x,n. y-s. y+1);

*)

Теперь представим себе, что эта библиотека считается собственностью некой компании, которая продает программное обеспечение, и что они продают вам только заголовочный файл, содержащий определения фигур, и откомпилированный вариант оределений функций. И у вас все равно остается возможность оределять новые фигуры и использовать для ваших собственных фигур сервисные функции.

7.6.3 Прикладная Программа

Прикладная программа чрезвычайно проста. Определяется новая фигура myshape (на печати она немного похожа на рожцу), а потом пишется главная программа, которая надевает на нее шляпу. Вначале описание myshape:

#include «shape. h»

class myshape : public rectangle (* line* l_eye; // левый глаз line* r_eye; // правый глаз line* mouth; // рот public: myshape(point, point); void draw(); void move(int, int); *);

Глаза и рот – отдельные и независимые объекты, которые создает конструктор myshape:

myshape::myshape(point a, point b) : (a, b) (* int ll = neast().x-swest().x+1; int hh = neast().y-swest().y+1; l_eye = new line( point(swest().x+2,swest().y+hh*3/4),2); r_eye = new line( point(swest().x+ll-4,swest().y+hh*3/4),2); mouth = new line( point(swest().x+2,swest().y+hh/4),ll-4); *)

Объекты глаза и рот порознь рисуются заново функцией shape_refresh(), и в принципе могут обрабатываться независимо из объекта myshape, которому они принадлежат. Это один способ определять средства для иерархически построенных объектов вроде myshape. Другой способ демонстрируется на примере носа. Никакой нос не определяется, его просто добавляет к картинке функция draw():

void myshape::draw() (* rectangle::draw(); put_point(point( (swest().x+neast().x)/2,(swest().y+neast().y)/2)); *)

myshape передвигается посредством перемещения базового прямоугольника rectangle и вторичных объектов l_eye, r_eye и mouth (левого глаза, правого глаза и рта):

void myshape::move() (* rectangle::move(); l_eye-»move(a, b);

r_eye-»move(a, b); mouth-»move(a, b); *)

Мы можем, наконец, построить несколько фигур и немного их подвигать:

main() (* shape* p1 = new rectangle(point(0,0),point(10,10)); shape* p2 = new line(point(0,15),17); shape* p3 = new myshape(point(15,10),point(27,18)); shape_refresh(); p3-»move(-10,-10); stack(p2,p3); stack(p1,p2); shape_refresh(); return 0; *)

Еще раз обратите внимание, как функции вроде shape_refresh() и stack() манипулируют объектами типов, опрделяемых гораздо позже, чем были написаны (и, может быть, окомпилированы) сами эти функции.

*** * * * * * * * * * * * * * * * * * * *** * ***** * * * ** ** * * * * * * * * * * * * * *****

7.7 Свободная Память

Если вы пользовались классом slist, вы могли обнаружить, что ваша программа тратит на заметное время на размещение и освобождение объектов класса slink. Класс slink – это превоходный пример класса, который может значительно выиграть от того, что программист возьмет под контроль управление свобоной памятью. Для этого вида объектов идеально подходит оптмизирующий метод, который описан в #5.5.6. Поскольку каждый slink создается с помощью new и уничтожается с помощью delete членами класса slist, другой способ выделения памяти не представляет никаких проблем.

Если производный класс осуществляет присваивание указтелю this, то конструктор его базового класса будет вызыватся только после этого присваивания, и значение указателя this в конструкторе базового класса будет тем, которое присвоено конструктором производного класса. Если базовый класс присвивает указателю this, то будет присвоено то значение, которое использует конструктор производного класса. Например:

#include «stream. h»

struct base (* base(); *);

struct derived : base (* derived(); *)

base::base() (* cout «„ „\tbase 1: this=“ „„ int(this) «« «\n“; if (this == 0) this = (base*)27; cout «« «\tbase 2: this=“ «« int(this) «« «\n“; *)

derived::derived() (* cout «„ „\tderived 1: this=“ „„ int(this) «« «\n“; this = (this == 0) ? (derived*)43 : this; cout «« «\tderived 2: this=“ «« int(this) «« «\n“; *)

main() (* cout «„ „base b;\n“; base b; cout „„ „new base b;\n“; new base; cout «« «derived d;\n“; derived d; cout «« «new derived d;\n“; new derived; cout «« «at the end\n“;

*)

порождает вывод

base b; base 1: this= base 2: this= new base; base 1: this=0 base 2: this=27 derived d; derived 1: this= base 1: this= base 2: this= derived 1: this= new derived; derived 1: this=0 base 1: this=43 base 2: this=43 derived 1: this=43 at the end

Если деструктор производного класса осуществляет присвивание указателю this, то будет присвоено то значение, котрое встретил деструктор его базового класса. Когда кто-либо делает в конструкторе присваивание указателю this, важно, чтобы присваивание указателю this встречалось на всех путях в конструкторе*.

– * К сожалению, об этом присваивании легко забыть. Напрмер, в первом издании этой книги (английском – перев.) вторая строка конструктор derived::derived() читалась так:

if (this == 0) this = (derived*)43;

И следовательно, для d конструктор базового класса base::base() не вызывался. Программа была допустимой и коректно выполнялась, но, очевидно, делала не то, что подразмевал автор. (прим. автора)

7.8 Упражнения

1. (*1) Определите

class base (* public: virtual void iam() (* cout «„ «base\n“; *) *);

Выведите из base два класса и для каждого определите iam () («я есть»), которая выводит имя класса на печать. Создайте объекты этих классов и вызовите для них iam(). Присвойте адреса объектов производных классов указателям base* и вызовите iam() через эти указатели.

2. (*2) Реализуйте примитивы экрана (#7.6.1) подходящим для вашей системы образом.

3. (*2) Определите класс triangle (треугольник) и класс circle (круг).

4. (*2) Определите функцию, которая рисует линию, соединящую две фигуры, отыскивая две ближайшие «точки соприконовения» и соединяя их.

5. (*2) Модифицируйте пример с фигурами так, чтобы line бла rectangle и наоборот.

6. (*2) Придумайте и реализуйте дважды связанный список, который можно использовать без итератора.

7. (*2) Придумайте и реализуйте дважды связанный список, которым можно пользоваться только посредством итератора. Итератор должен иметь действия для движения вперед и нзад, действия для вставления и удаления элементов спика, и способ доступа к текущему элементу.

8. (*2) Постройте обобщенный вариант дважды связанного списка.

9. (*4) Сделайте список, в котором вставляются и удаляются сами объекты (а не просто указатели на объекты). Продлайте это для класса X, для которого определены X::X(X amp;), X::~X() X::operator=(X amp;).

10. (*5) Придумайте и реализуйте библиотеку для написания моделей, управляемых прерываниями. Подсказка: «task. h». Только это – старая программа, а вы могли бы написать лучше. Должен быть класс task – задача. Объект класса task должен мочь сохранять свое состояние и восстанавлваться в это состояние (вы можете определить task::save() и task::restore()), чтобы он мог действвать как сопрограмма. Отдельные задачи можно определять как объекты классов, производных от класса task. Прорамма, которую должна исполнять задача, может задаваться как виртуальная функция. Должна быть возможность передвать новой задаче ее параметры как параметры ее контруктора(ов). Там должен быть планировщик, реализующий концепцию виртуального времени. Обеспечьте функцию здержки task::delay(), которая «тратит» виртуальное время. Будет ли планировщик отдельным или частью класса task – это один из основных вопросов, которые надо ршить при проектировании. Задача должна передавать даные. Для этого разработайте класс queue (очередь). Прдумайте способ, чтобы задача ожидала ввода из нескольких очередей. Ошибки в ходе выполнения обрабатывайте однродным образом. Как бы вы отлаживали программы, написаные с помощью такой библиотеки?

Глава 8 Потоки

``bad input char: .Ppm(*=P!..*@Z9A*)5!!!!!"syui!!!"!Mp#V6P? p8`;!4lf amp;

сообщение об ошибке (сокращенное)

Язык С++ не обеспечивает средств для ввода/вывода. Ему это и не нужно. Такие средства легко и элегантно можно содать с помощью самого языка. Описанная здесь стандартная билиотека потокового ввода/вывода обеспечивает гибкий и эффетивный, с гарантией типа, метод обработки символьного ввода целых чисел, чисел с плавающей точкой и символьных строк, а также простую модель ее расширения для обработки типов, опрделяемых пользователем. Ее пользовательский интерфейс нахдится в «stream. h». В этой главе описывается сама библиотека, некоторые способы ее применения и методы, которые использовлись при ее реализации.

8.1 Введение

Разработка и реализация стандартных средств ввода/вывода для языка программирования зарекомендовала себя как заведомо трудная работа. Традиционно средства ввода/вывода разрабатвались исключительно для небольшого числа встроенных типов данных. Однако в С++ программах обычно используется много тпов, определяемых пользователем, и нужно обрабатывать ввод и вывод также и значений этих типов. Очевидно, средство ввда/вывода должно быть простым, удобным, надежным в употреблнии, эффективным и гибким, и ко всему прочему полным. Ничье решение еще не смогло угодить всем, поэтому у пользователя должна быть возможность задавать альтернативные средства ввда/вывода и расширять стандартные средства ввода/вывода прменительно к требованиям приложения.

С++ разработан так, чтобы у пользователя была возмоность определять новые типы столь же эффективные и удобные, сколь и встроенные типы. Поэтому обоснованным является требвание того, что средства ввода/вывода для С++ должны обеспчиваться в С++ с применением только тех средств, которые дотупны каждому программисту. Описываемые здесь средства ввода/ вывода представляют собой попытку ответить на этот вызов.

Средства ввода/вывода «stream. h» связаны исключительно с обработкой преобразования типизированных объектов в последвательности символов и обратно. Есть и другие схемы ввода/ввода, но эта является основополагающей в системе UNIX, и большая часть видов двоичного ввода/вывода обрабатывается чрез рассмотрение символа просто как набора бит, при этом его общепринятая связь с алфавитом игнорируется. Тогда для прораммиста ключевая проблема заключается в задании соответствия между типизированным объектом и принципиально нетипизированой строкой.

Обработка и встроенных и определяемых пользователем тпов однородным образом и с гарантией типа достигается с пмощью одного перегруженного имени функции для набора функций вывода. Например:

put(cerr,"x = «); // cerr – поток вывода ошибок put(cerr, x); put(cerr,»\n");

Тип параметра определяет то, какая из функций put будет вызываться для каждого параметра. Это решение применялось в нескольких языках. Однако ему недостает лаконичности. Перерузка операции «„ значением «поместить в“ дает более хорошую

запись и позволяет программисту выводить ряд объектов одним оператором. Например:

cerr «„ "x = " «« x «« «\n“;

где cerr – стандартный поток вывода ошибок. Поэтому, ели x является int со значением 123, то этот оператор напечтает в стандартный поток вывода ошибок

x = 123

и символ новой строки. Аналогично, если X принадлежит определенному пользователем типу complex и имеет значение (1, 2.4), то приведенный выше оператор напечатает в cerr

x = (1,2.4)

Этот метод можно применять всегда, когда для x определна операция ««, и пользователь может определять операцию «« для нового типа.

8.2 Вывод

В этом разделе сначала обсуждаются средства форматного и бесформатного вывода встроенных типов, потом приводится стадартный способ спецификации действий вывода для определяемых пользователем типов.

8.2.1 Вывод Встроенных Типов

Класс ostream определяется вместе с операцией «„ («пместить в“) для обработки вывода встроенных типов:

class ostream (* // ... public: ostream amp; operator««(char*); ostream amp; operator««(int i) (* return *this««long(i); *) ostream amp; operator««(long); ostream amp; operator««(double);

ostream amp; put(char); *);

Функция operator«« возвращает ссылку на ostream, для кторого она была вызвана, чтобы к ней можно было применять другой ostream. Например:

cerr «« "x = " «« x;

где x является int, будет интерпретироваться как:

(cerr. operator««("x = ")).operator««(x);

В частности, отсюда следует, что когда один оператор ввода печатает несколько элементов, они будут печататься в ожидаемом порядке: слева направо. Наличие operator««, которая получает int, является избыточным, поскольку int может неявно преобразовываться в long. С другой стороны, int может преоразовываться также и в double. Наличие ostream::operator««(int) позволяет избежать этой неоднознаности. Для печати символов в виде символов предоставляется функция ostream::put(char), а ostream::operator««(int) печтает их целые значения.

8.2.2 Вывод Определяемых Пользователем Типов

Рассмотрим определяемый пользователем тип:

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;); // ... *);

Функции ввода определяются в таком духе:

Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16