объектов, находящихся в списке (что становится ивестно только на стадии выполнения), а не просто от типа элментов списка (который компилятору известен).

7.6 Законченная Программа

Разберем процесс написания программы для рисования на экране геометрических фигур. Она естественным образом раздляется на три части:

1. Администратор экрана: подпрограммы низкого уровня и структуры данных, определяющие экран;он ведает только точками и прямыми линиями,

2. Библиотека фигур: набор определений основных фигур вроде прямоугольника и круга и стандартные программы для работы с ними и

3. Прикладная программа: множество определений, специалзированных для данного приложения, и код, который их использует.

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

7.6.1 Администратор Экрана

Вначале было намерение написать администратор экрана на C (а не на С++), чтобы подчеркнуть разделение уровней реалзации. Это оказалось слишком утомительным, поэтому пришлось пойти на компромисс: используется стиль C (нет функций члнов, виртуальных функций, определяемых пользователем операций и т.п.), однако применяются конструкторы, надлежащим образом описываются и проверяются параметры функций и т.д. Оглядывясь назад, можно сказать, что администратор экрана очень пхож на C программу, которую потом модифицировали, чтобы вопользоваться средствами С++ не переписывая все полностью.

Экран представляется как двумерный массив символов, работу с которым осуществляют функции put_point() и put_line(), использующие при обращении с экраном структуру point:

// файл screen.h

const XMAX=40, YMAX=24;

struct point (* int x,y; point() (**) point(int a, int b) (* x=a; y=b; *) *);

overload put_point; extern void put_point(int a, int b); inline void put_point(point p) (* put_point(p.x,p.y); *)

overload put_line; extern void put_line(int, int, int, int); inline void put_line(point a, point b) (* put_line (a.x,a.y,b.x,b.y); *)

extern void screen_init(); extern void screen_refresh(); extern void screen_clear();

#include «stream.h»

Перед первым использованием функции put экран надо инциализировать с помощью screen_init(), а изменения в структре данных экрана отображаются на экране только после вызова screen_refresh(). Как увидит пользователь, это «обновление» («refresh») осуществляется просто посредством печати новой копии экрана под его предыдущим вариантом. Вот функции и оределения данных для экрана:

#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(' '); *) *)

Функция 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()

Вы читаете C++
Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

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

Отметить Добавить цитату