- •Д. Н. Лясин, с. Г. Саньков
- •Оглавление
- •1.Обзор стилей программирования
- •1.1. Процедурное программирование
- •Структурное программирование
- •Функциональное программирование
- •Логическое программирование
- •1.5. Объектно-ориентированное программирование
- •Основные принципы объектно-ориентированного программирования
- •3.1. Объявление классов и объектов
- •3.2. Конструкторы и деструкторы
- •3.3. Область видимости компонент класса
- •3.4. Определение компонентных функций класса
- •3.5. Статические компоненты классов
- •Дружественные функции
- •3.7. Перегрузка операций
- •4. Наследование классов
- •4.1. Повторное использование классов: наследование и агрегирование
- •4.3. Множественное наследование
- •4.4. Виртуальные классы
- •4.5. Виртуальные функции. Полиморфизм
- •4.6. Абстрактные классы
- •Список литературы
4.4. Виртуальные классы
В некоторых случаях дублирование компонент непрямого базового класса необходимо устранить. Например, перед нами стоит задача определить классы, описывающие поведение шахматных фигур.
//Листинг 27. Определение классов «шахматные фигуры»
class Figure //класс «фигура»
{ protected:
int hor; //позиция фигуры по горизонтали
char vert; //позиция фигуры по вертикали
int color; //цвет фигуры
public:
Figure(char x, int y, int z) //конструктор
: vert(x), hor(y), color(z)
{}
};
class Castle : public Figure //класс ладья
{ public:
Сastle(char x, int y, int z):
Figure (x, y, z) //конструктор
{}
int Move(char x, int y) //функция, реализующая ход ладьи на поле [x y ]
{ if ( ((x == vert)
&& (y != hor))
|| ((x != vert)
&& (y == hor)))
{
hor = y;
vert = x;
return 1;
}
return 0;
}
};
class Bishop : public Figure //класс слон
{ public:
Bishop(char x, int y, int z):
Figure (x, y, z) //конструктор
{}
int Move(char x, int y) //функция, реализующая ход слона на поле [x y ]
{ if (abs((x - vert)
== abs(y - hor))
&& (x != vert)) {
vert= x; y=hor;
return 1;
}
return 0;
}
};
class Queen: public Bishop, public Castle
{
public:
Queen(char x, int y, int z): //конструктор
Castle (x, y, z), Bishop (x, y, z)
{}
int Move(char x, int y) {
return Castle::Move(x,y) || Bishop::Move (x,y);
}};
В программе определены 4 класса. Класс Figure является абстрактным обобщением свойств всех шахматных фигур, поэтому он содержит такие компонентные данные, как позиция фигуры на доске, определяемая по вертикали буквой vert и по горизонтали цифрой hor, а также цвет фигуры color. Классы Castle и Bishop описывают, соответственно, ладью и слона. Для этих классов определена функция
int Move(char x, int y),
п роверяющая, может ли данная фигура пойти на поле с указанными в параметрах функции координатами, и если может – сделать этот ход. Самым интересным классом является класс Queen, описывающий поведение ферзя. Каждый, кто знаком с правилами шахмат, знает, что ферзь объединяет в себе свойства ладьи и слона (в том смысле, что может ходить как по диагонали, как слон, так и по вертикали и горизонтали, как ладья). Поэтому класс Queen объявлен потомком двух классов: Castle и Bishop. Таким образом, имеется иерархия классов, изображенная на рис.10.
Приведенный пример реализации класса Queen содержит ошибку. Дело в том, что для класса Queen дублируются компонентные данные vert, hor и color, в то время как у реального ферзя всего одна позиция на доске и один цвет. Таким образом, встала задача предотвратить дублирование компонент непрямого базового класса в производном. Решить эту проблему можно, объявив класс Figure виртуальным.
Для того, чтобы определить непрямой базовый класс виртуальным, необходимо при объявлении этого класса базовым в списке порождения указать ключевое слово virtual. Спецификатор virtual способствует минимизации структуры производного класса. Главная особенность виртуальных базовых классов - они не тиражируются. Изменим определение классов шахматных фигур.
class Figure {…};
class Castle: public virtual Figure {…};
class Bishop: public virtual Figure {…};
class Castle: public Castle, public Bishop{…};
Теперь схема иерархии классов выглядит так, как показано на рис.11, и компонентные данные vert, hor, color не будут дублироваться в объектах класса Queen.
При использовании виртуальных классов необходимо обратить внимание на особенность вызова конструкторов базовых классов. Конструктор класса Queen был определен следующим образом:
Queen(char x, int y, int z): Castle (x, y, z), Bishop (x, y, z)
{}
В конструкторе предусмотрен вызов конструкторов прямых базовых классов, которые создают в памяти экземпляры классов Castle и Bishop . В свою очередь, конструкторы классов Castle и Bishop вызывают конструктор своего базового класса (Figure), вследствие чего и создавалось два экземпляра класса Figure. Если класс Figure объявлен виртуальным, конструктор класса Queen необходимо переопределить:
Queen(char x, int y, int z): Castle (x, y, z), Bishop (x, y, z), Figure(x,y,z)
{}
Если Figure – виртуальный класс, то конструкторы классов Castle и Bishop не будут вызывать конструктор класса Figure, а для того, чтобы один экземпляр этого объекта все таки был создан, вызов конструктора класса Figure необходимо поместить непосредственно в определение класса Queen.
При множественном наследовании существует возможность определять один и тот же базовый класс как косвенный базовый для другого класса несколько раз, причем и как виртуальный, и как невиртуальный. Рассмотрим следующий пример:
class A {…};
class B: virtual public A {…};
class C: virtual public A {…};
class D: public A{…};
class E: public A {…};
class F: public B, public C, public D, public E {…};
Схема иерархии для такой системы классов в графической форме приведена на рис.12.
В данном случае объект класса F будет включать три экземпляра класса A: один виртуальный, совместно используемый классами B и С, и два невиртуальных, относящихся к классам E и D.