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
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;
Линию можно построить либо по двум точкам, либо по точке и целому.
В последнем случае создается горизонтальная линия, длину которой
определяет целое. Знак целого указывает, каким концом является
точка: левым или правым. Вот определение:
- стр 225 -
class line : public shape {
/*
линия из 'w' в 'e'
north() определяется как ``выше центра
и на север как до самой северной точки''
*/
point w,e;
public:
point north()
{ return point((w.x+e.x)/2,e.ydraw();
screen_refresh();
}
- стр 227 -
И вот, наконец, настоящая сервисная функция (утилита). Она кладет
одну фигуру на верх другой, задавая, что 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 Прикладная Программа
Прикладная программа чрезвычайно проста. Определяется новая
фигура 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(), и в принципе могут обрабатываться независимо из
- стр 228 -
объекта 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() манипулируют объектами типов, определяемых гораздо позже,
чем были написаны (и, может быть, откомпилированы) сами эти
функции.
- стр 229 -
***********
* *
* *
* *
* *
* *
* *
* *
* *
* *
***********
*****************
*************
* *
* ** ** *
* *
* * *
* *
* ********* *
* *
*************
7.7 Свободная Память
Если вы пользоались классом slist, вы могли обнаружить, что ваша
программа тратит на заметное время на размещение и освобождение
объектов класса slink. Класс slink - это превосходный пример
класса, который может значительно выиграть от того, что программист
возьмет под контроль управление свободной памятью. Для этого вида
объектов идеально подходит оптимизирующий метод, который описан в
#5.5.6. Поскольку каждый slink создается с помощью new и
уничтожается с помощью delete членами класса slist, другой способ
выделения памяти не представляет никаких проблемм.
Если производный класс осуществляет присваивание указателю this,
то конструктор его базового класса будет вызываться только после
этого присваивания, и значение указателя this в конструкторе
базового класса будет тем, которое присвоено конструктором
производного класса. Если базовый класс присваивает указателю this,
то будет присвоено то значение, которое использует конструктор
производного класса. Например:
- стр 230 -
#include
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=2147478307
base 2: this=2147478307
new base;
base 1: this=0
base 2: this=27
derived d;
derived 1: this=2147478306
base 1: this=2147478306
base 2: this=2147478306
derived 1: this=2147478306
new derived;
derived 1: this=0
base 1: this=43
base 2: this=43
derived 1: this=43
at the end
Если деструктор производного класса осуществляет присваивание
указателю this, то будет присвоено то значение, которое всретил
- стр 231 -
деструктор его базового класса. Когда кто-либо делает в
конструкторе присваивание указателю this, важно, чтобы присваивание
указателю this всречалось на всех путях в конструкторе*.
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&), X::~X()
X::operator=(X&).
10. (*5) Придумайте и реализуйте библиотеку для написания
моделей, управляемых прерываниями. Подсказка: . Только
это - старая программа, а вы могли бы написать лучше. Должен
быть класс task (- задача). Объект класса task должен мочь
сохранять свое состояние и восстанавливаться в это состояние
(вы можете определить task::save() и task::restore()), чтобы
____________________