<< Предыдущая Оглавление Следующая >>


6.2. Наследование

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

Предположим, создается дочерний класс с именем CLine для работы с линией на основе базового CPos. Для этого после имени дочернего класса CLine ставится символ ‘:’, а затем пишется имя базового класса CPos с указанием уровня доступа:

class CPos
{
public:
CPos() {}
CPos(int x1, int y1, int x2, int y2) {SetParam(x1,y1,x2,y2);}
~CPos() {}

void SetParam(int x1, int y1, int x2, int y2);

protected:
int sp_x, sp_y;
int ep_x, ep_y;
};

class CLine : public CPos
{
public:
CLine() {}
CLine(int x1,int y1, int x2, int y2) {SetParam(x1,y1,x2,y2);}
~CLine() {}

void Draw() {MoveTo(sp_x,sp_y); LineTo(ep_x,ep_y);}
};

В результате наследования с уровнем доступа public класс CLine имеет доступ ко всем переменным и функциям класса CPos, которые не являются частными (private). Ключевое слово public перед именем класса CPos означает, что все общие (public) элементы этого класса остаются с таким же уровнем доступа и в классе CLine. Следует также отметить, что описание класса CPos должно предшествовать описанию класса CLine, а переменные sp_x, sp_y, ep_x и ep_y должны быть описаны в разделе protected для возможности их использования в функции Draw() дочернего класса CLine и в то же время не доступными извне.

Класс CLine содержит два конструктора, деструктор и функцию Draw() для рисования линии на экране. При этом процедура задания координат графического объекта целиком находится в базовом классе CPos и по мере необходимости используется в дочернем классе CLine. Такое разделение оказывается удобным, т.к. при описании работы с новыми графическими объектами процедура работы с их координатами будет оставаться одной и той же и находится в одном классе. Если бы в данном случае использовался структурный подход к программированию, то алгоритм работы с координатами графических примитивов пришлось бы прописывать каждый раз для всех типов объектов, что привело бы к заметному усложнению текста программы.

Для работы с дочерним классом, также как и с обычным, необходимо создать его экземпляр либо с помощью оператора new, либо через представитель, как показано ниже:

CLine* line_ptr = new CLine();

или

CLine line;

При создании нового объекта CLine вызывается сначала конструктор CPos() базового класса, а затем конструктор дочернего – CLine(). Таким образом, создается как бы два объекта: CPos и CLine, но они представляются как единое целое объекта CLine.

В представленном классе CLine предусмотрено два конструктора: с параметрами и без них. В случае вызова конструктора с параметрами

CLine line(10,10,20,20);

вызывается конструктор CPos() базового класса, а затем конструктор CLine(int x1, int y1, int x2, int y2) дочернего, в котором выполняется функция SetParam() для записи значений координат графического объекта.

Последние два приведенных примера создания объекта CLine показывают, что вне зависимости от типа вызываемого конструктора дочернего класса всегда вызывается один и тот же конструктор CPos() базового класса, даже если в последнем определено несколько конструкторов. Это не всегда удобно и кроме того, если конструктор CPos() не описан в базовом классе, то создание дочернего класса CLine станет невозможным, т.к. конструктор по умолчанию CPos() не будет найден. Для того чтобы исправить такую ситуацию необходимо в дочернем классе указать, какой именно конструктор базового класса следует вызывать, следующим образом:

class CLine : public CPos
{
public:
CLine() : CPos()
{
}
CLine(int x1,int y1, int x2, int y2) : CPos(x1,y1,x2,y2)
{
}
~CLine() {}

void Draw() {MoveTo(sp_x,sp_y); LineTo(ep_x,ep_y);}
};

В приведенном примере конструктор CLine() будет вызывать конструктор CPos() базового класса, а конструктор CLine(int x1, int y1, int x2, int y2) конструктор CPos(int x1, int y1, int x2, int y2). При этом функция SetParam() в CLine(int x1, int y1, int x2, int y2) может быть опущена, т.к. необходимая инициализация переменных будет выполнена при вызове конструктора CPos(int x1, int y1, int x2, int y2) базового класса.

В рассматриваемой задаче программирования графического редактора, класс CPos является вспомогательным, т.е. он служит для создания описания новых классов как базовый. При этом нет необходимости создавать его экземпляр в памяти ЭВМ для непосредственной работы с ним. Поэтому целесообразно защитить его от возможности создания путем помещения конструкторов данного класса в раздел protected. Такие классы называются абстрактными, т.е. они не могут существовать как самостоятельные объекты, а служат для создания новых, дочерних классов. Описание абстактного класса CPos и дочернего от него CLine показано ниже:

class CPos
{
protected:
CPos() {}
CPos(int x1, int y1, int x2, int y2) {SetParam(x1,y1,x2,y2);}
~CPos() {}

public:
void SetParam(int x1, int y1, int x2, int y2);

protected:
int sp_x, sp_y;
int ep_x, ep_y;
};

Функции классов CPos и CLine можно вызывать, например, через представитель класса CLine, следующим образом:

CLine line;
line.SetParam(10,10,20,20);
line.Draw();

Обратите внимание, что благодаря полиморфизму, функция SetParam(), заданная в классе CPos, вызывается через представитель line как будто она определена в классе CLine. В результате, единожды объявленная функция SetParam() может быть многократно использована в разных классах, производных от CPos.

Для работы с другими графическими примитивами (прямоугольником и эллипсом) подобным образом можно создать дочерние классы от CPos, отличающихся друг от друга реализацией функции Draw():

class CRect : public CPos
{
public:
CRect() : CPos()
{
}
CRect(int x1,int y1, int x2, int y2) : CPos(x1,y1,x2,y2)
{
}
~CRect() {}

void Draw() {Rectangle(sp_x,sp_y,ep_x,ep_y);}
};

class CEllipse : public CPos
{
public:
CEllipse() : CPos()
{
}
CEllipse(int x1,int y1, int x2, int y2) : CPos(x1,y1,x2,y2)
{
}
~CEllipse() {}

void Draw() {Ellipse(sp_x,sp_y,ep_x,ep_y);}
};

В результате построения объектов получается следующая иерархия (рис. 6.1).

Рис. 6.1. Иерархия классов графических примитивов

У каждого из представленных дочерних объектов CLine, CRect и CEllipse имеется один базовый объект CPos. Вместе с тем язык С++ предоставляет возможность создавать дочерние объекты на основе нескольких базовых, что приводит к концепции множественного наследования.

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

class CProp
{
protected:
CProp() {}
CProp(int wdt, int clr) {SetProperty(wdt,clr);}
~CProp();

public:
void SetProperty(int wdt, int clr)
{
if(wdt >= 0 && wdt <= MAX_WIDTH) width = wdt;
if(clr >= 0 && clr <= MAX_COLOR) color = clr;
}
protected:
int width, color;
};

Теперь дочерние классы CLine, CRect и CEllipse можно образовывать от двух базовых CPos и CProp, которые являются не связанными друг с другом. Для того чтобы построить класс на основе двух базовых они указываются друг за другом через запятую следующим образом:

class CLine : public CPos, public CProp
{
public:
CLine() : CPos(), CProp() {}
CLine(int x1, int y1, int x2, int y2, int w, int clr) :
CPos(x1,y1,x2,y2), CProp(w,clr) {}
~CLine() {}

void Draw() {SetWidth(width); SetColor(color);
MoveTo(sp_x, sp_y); LineTo(ep_x, ep_y);
}
};

Аналогичным образом строятся классы CRect и CEllipse. Здесь следует отметить, что конструктор CLine(int x1, int y1, int x2, int y2, int w, int clr) класса CLine вызывает конструкторы двух базовых классов, которые перечислены через запятую с указанием в них конкретных переменных. Работа с функциями класса CLine через его представитель имеет следующий вид:

CLine line;
line.SetProperty(1,0);
line.SetParam(10,10,20,20);
line.Draw();

Благодаря полиморфизму, функции SetProperty() и SetParam() базовых классов вызываются непосредственно из класса CLine.

Видео по теме

С++ с нуля: урок 1 - переменные, оператор присваивания

С++ с нуля: урок 2 - арифметические операции

С++ с нуля: урок 3 - директивы препроцессора

С++ с нуля, урок 4: условные операторы if и switch

С++ с нуля: урок 5 - операторы циклов while, for и do while

С++ с нуля: урок 6 - массивы, метод всплывающего пузырька

С++ с нуля: урок 7 - строки и функции работы с ними

С++ с нуля: урок 8 - функции: прототипы, перегрузка, рекурсия

С++ с нуля: урок 9 - области видимости переменных

С++ с нуля: урок 10 - битовые операции И, ИЛИ, НЕ, XOR

С++ с нуля: урок 11 - структуры

С++ с нуля: урок 12 - объединения, перечисления, typedef

С++ с нуля: урок 13 - указатели и ссылки, выделение памяти

С++ с нуля: урок 14 (часть 1) - функции работы с файлами

С++ с нуля: урок 14 (часть 2) - функции работы с файлами

С++ с нуля: урок 15 - стек, теория и практика

С++ с нуля: урок 16 - связные списки, теория и практика

С++ с нуля: урок 17 - бинарное дерево, теория и практика



<< Предыдущая Оглавление Следующая >>