Разберем процесс написания программы для рисования на экране геометрических фигур. Она естественным образом разделяется на три части:
Администратор экрана: подпрограммы низкого уровня и структуры данных, определяющие экран; он ведает только точками и прямыми линиями;
Библиотека фигур: набор определений основных фигур вроде прямоугольника и круга и стандартные программы для работы с ними; и
Прикладная программа: множество определений, специализированных для данного приложения, и код, в котором они используются.
Эти три части скорее всего будут писать разные люди (в разных организациях и в разное время). При этом части будут скорее всего писать именно в указанном порядке с тем осложняющим обстоятельством, что у разработчиков нижнего уровня не будет точного представления, для чего их код в конечном счете будет использоваться. Это отражено в приводимом примере. Чтобы пример был короче, графическая библиотека предоставляет только весьма ограниченный сервис, а сама прикладная программа очень проста. Чтобы читатель смог испытать программу, даже если у него нет совсем никаких графических средств, используется чрезвычайно простая концепция экрана. Не должно составить труда заменить эту экранную часть программы чем-нибудь подходящим, не изменяя код библиотеки фигур и прикладной программы.
Администратор Экрана
Вначале было намерение написать администратор экрана на C (а не на C++), чтобы подчеркнуть разделение уровней реализации. Это оказалось слишком утомительным, поэтому пришлось пойти на компромисс: используется стиль 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
Перед первым использованием функции put экран надо инициализировать с помощью screen_init(), а изменения в структуре данных экрана отображаются на экране только после вызова screen_refresh(). Как увидит пользователь, это "обновление" ("refresh") осуществляется просто посредством печати новой копии экрана под его предыдущим вариантом. Вот функции и определения данных для экрана:
#include "screen.h"
#include
enum color { black="*", white=" " };
char screen[XMAX][YNAX];
void screen_init()
{
for (int y=0; y=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
Библиотека ФигурНам нужно определить общее понятие фигуры (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.ydraw();
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);
}
Теперь представим себе, что эта библиотека считается собственностью некоей компании, которая продает программное обеспечение, и что они продают вам только заголовочный файл, содержащий определения фигур, и откомпилированный вариант определений функций. И у вас все равно остается возможность определять новые фигуры и использовать для ваших собственных фигур сервисные функции.
Прикладная ПрограммаПрикладная программа чрезвычайно проста. Определяется новая фигура my_shape (на печати она немного похожа на рожицу), а потом пишется главная программа, которая надевает на нее шляпу. Вначале описание my_shape:
#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);
};
Глаза и рот - отдельные и независимые объекты, которые создает конструктор my_shape:
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(), и в принципе могут обрабатываться независимо из объекта my_shape, которому они принадлежат. Это один способ определять средства для иерархически построенных объектов вроде my_shape. Другой способ демонстрируется на примере носа. Никакой нос не определяется, его просто добавляет к картинке функция draw():
void myshape::draw()
{
rectangle::draw();
put_point(point(
(swest().x+neast().x)/2,(swest().y+neast().y)/2));
}
my_shape передвигается посредством перемещения базового прямоугольника 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() манипулируют объектами типов, определяемых гораздо позже, чем были написаны (и, может быть, откомпилированы) сами эти функции.
Результатом работы программы будет:
*********** * * * * * * * * * * * * * * * * * * *********** ***************** ************* * * * ** ** * * * * * * * * * ********* * * * ************* |
Похожие работы
... особенностей эксплуатации машин; - использовать системы машин в типовых технологических процессах. 1.3 Связь дисциплины с другими предметами. Изучение "Системы машин в лесном хозяйстве и лесной промышленности" опирается на знание других специальных дисциплин: - тракторы и автомобили с основами технической механики; - лесохозяйственные машины и их применение; - лесная ...
... никакого строгого установления границ на заданиях. Когда проект закончен, группа расформирована. Рабочие назначены на их проекты. Анализ текста, переведенного программой PROMT Гигант При работе с данным программным продуктом были обнаружены следующие погрешности при переводе текста. Лексический анализ текста показал, что PROMT по большей части адекватно переводит простые части речи, но ...
... используются театрализация, различные жанры искусства, аттракционность воздействия оформленных средствами быстрой и непрерывной смены ощущений, неожиданностями и эксцентрическими разрешениями сценических ситуаций. Глава 2. Технологические этапы постановки концертно-зрелищных программ 2.1 Номер – основа технологии постановки концертно-зрелищных программ Теперь необходимо рассмотреть ...
... 12,9 6088090 Поросята на доращивании 3994 4 5831240 20 29156200 12 17493720 Откормочный молодняк 6463 6,5 1470332,5 97 22882251 32,3 76195538 Взрослые на Откорме 65 0,13 3084,25 1,3 30842,5 0,72 17082 13. Разработка генплана свиноводческого предприятия с законченным оборотом стада Архитектурно-строительные решения свиноводческих зданий должны обеспечивать оптимальные ...
0 комментариев