этого фигура перестанет быть окружностью. Нам не нужен объект, который иногда относится к одному типу (когда major()!=minor()), а иногда к другому (когда major()==minor()). Нам нужен объект (класса Ellipse), который иногда выглядит как окружность. С другой стороны, объект класса Circle никогда не превратится в эллипс с двумя неравными осями.
Разрабатывая класс, мы должны быть осторожными: не слишком умничать и не слишком полагаться на интуицию. И наоборот, должны быть уверены, что наш класс представляет некое осмысленное понятие, а не является просто коллекцией данных и функций-членов.
Механическое объединение фрагментов кода без размышлений об идеях и понятиях, которые они представляют, — это хакерство. Оно приводит к программам, которые невозможно объяснить и эксплуатировать без участия автора. Если вы не альтруист, то помните, что в роли ничего не понимающего пользователя через несколько месяцев можете оказаться вы сами. Кроме того, такие программы труднее отлаживать.
13.14. Класс Marked_polyline
Часто возникает необходимость пометить точки графика. График можно изобразить в виде ломаной, поэтому нам нужна ломаная, точки которой имели бы метки. Для этого предназначен класс Marked_polyline. Рассмотрим пример.
Marked_polyline mpl("1234");
mpl.add(Point(100,100));
mpl.add(Point(150,200));
mpl.add(Point(250,250));
mpl.add(Point(300,200));
В результате выполнения этого фрагмента программы получим следующий результат:
Определение класса Marked_polyline имеет следующий вид:
struct Marked_polyline:Open_polyline {
Marked_polyline(const string& m):mark(m)
{
if (m=="") mark = "*";
}
void draw_lines() const;
private:
string mark;
};
Поскольку этот класс является наследником класса Open_polyline, можем свободно обрабатывать объекты класса Point, и все что нам для этого необходимо — иметь возможность ставить метки. В частности, функция draw_lines() примет следующий вид:
void Marked_polyline::draw_lines() const
{
Open_polyline::draw_lines();
for (int i=0; i<number_of_points(); ++i)
draw_mark(point(i),mark[i%mark.size()]);
}
Вызов функции Open_polyline::draw_lines() рисует линии, так что остается просто расставить метки. Эти метки представляют собой строки символов, которые используются в определенном порядке: команда mark[i%mark.size()] выбирает символ, который должен быть использован следующим, циклически перебирая символы, хранящиеся в объекте класса Marked_polyline. Оператор % означает деление по модулю (взятие остатка). Для вывода буквы в заданной точке функция draw_lines() использует вспомогательную функцию меньшего размера draw_mark().
void draw_mark(Point xy, char c)
{
static const int dx = 4;
static const int dy = 4;
string m(1,c);
fl_draw(m.c_str(),xy.x–dx,xy.y+dy);
}
Константы dx и dy используются для центрирования буквы относительно заданной точки. Объект m класса хранит единственный символ c.
13.15. Класс Marks
Иногда необходимо вывести метки отдельно от линий. Для этого предназначен класс Marks. Например, мы можем пометить четыре точки, использованные в предыдущих примерах, не соединяя их линиями.
Marks pp("x");
pp.add(Point(100,100));
pp.add(Point(150,200));
pp.add(Point(250,250));
pp.add(Point(300,200));
В итоге будет получено следующее изображение:
Очевидно, что класс Marks можно использовать для отображения дискретных данных, изображать которые с помощью ломаной было бы неуместно. В качестве примера можно привести пары (рост, вес), характеризующие группу людей.
Класс Marks — это разновидность класса Marked_polyline с невидимыми линиями.
struct Marks : Marked_polyline {
Marks(const string& m) :Marked_polyline(m)
{
set_color(Color(Color::invisible));
}
};
13.16. Класс Mark
Объект класса Point задает координаты в объекте класса Window. Иногда мы их отображаем, а иногда нет. Если возникает необходимость пометить отдельную точку, чтобы ее увидеть, мы можем изобразить ее в виде крестиков, как показано в разделе 13.2, или воспользоваться классом Marks. Это объяснение слегка многословно, поэтому рассмотрим простой объект класса Marks, инициализированный точкой и символом.
Например, мы могли бы пометить центры окружностей, изображенных в разделе 13.12, следующим образом:
Mark m1(Point(100,200),'x');
Mark m2(Point(150,200),'y');
Mark m3(Point(200,200),'z');
c1.set_color(Color::blue);
c2.set_color(Color::red);
c3.set_color(Color::green);
В итоге мы получили бы изображения, приведенные ниже.
Класс Mark — это разновидность класса Marks, в котором при создании объекта немедленно задается начальная (и, как правило, единственная) точка.
struct Mark : Marks {
Mark(Point xy, char c) : Marks(string(1,c))
{
add(xy);
}
};
Функция string(1,c) — это конструктор класса string, инициализирующий строку, содержащую единственный символ c.
Класс Mark всего лишь позволяет легко создать объект класса Marks с единственной точкой, помеченной единственным символом. Стоило ли тратить силы, чтобы определять такой класс? Или он является следствием “ложного стремления к усложнениям и недоразумениям”? Однозначного и логичного ответа на этот вопрос нет. Мы много думали над этим и в конце концов решили, что для пользователей этот класс был бы полезен, а определить его было совсем нетрудно.
Почему в качестве метки используется символ? Можно было бы нарисовать любую маленькую фигуру, но символы нагляднее и проще. Они часто позволяют отделить одно множество точек от другого. К тому же такие символы, как x, o, + и *, обладают центральной симметрией.
13.17. Класс Image
Файлы в типичном персональном компьютере хранят тысячи изображений. Кроме того, миллионы изображений доступны в сети веб. Естественно, мы хотели бы отображать содержимое этих файлов на экране с помощью относительно простых программ. Например, ниже продемонстрирован рисунок (rita_path.gif), иллюстрирующий путь урагана “Рита”, пришедшего из Мексиканского залива.
Мы можем выбрать часть этого изображения и добавить фотографию урагана, сделанную из космоса (rita.jpg).
Image rita(Point(0,0),"rita.jpg");
Image path(Point(0,0),"rita_path.gif");
path.set_mask(Point(50,250),600,400); // выбираем желательную область
win.attach(path);
win.attach(rita);
Операция set_mask() выбирает часть рисунка, которую следует изобразить на экране. В данном случае мы выбрали изображение размером 600×400 пикселей из файла rita_path.gif (загруженный как объект path) и показали его в области, левый верхний угол которой имеет координаты (50,250). Выбор части рисунка — довольно распространенный прием, поэтому мы предусмотрели для него отдельную операцию.