Язык С++ позволяет наследовать структуру (элементы данных) и поведение
(элементы-функции) одного или нескольких классов в другом. Новый класс
называют производным классом. Класс, элементы которого наследуются,
называется базовым классом. Производный класс, в свою очередь, может
выступать базовым классом для другого класса.
Наследование даёт возможность заключить некоторое общее или схожее
поведение различных объектов в одном базовом классе, представив так
называемую абстракцию для производных классов. Например, класс «фигура»
является абстракцией (отсутствующий в реальном мире объект). В свою
очередь, производные классы, такие как «Прямоугольник», «Треугольник» и
т.д. являются реальными воплощениями своего базового класса.
Наследование также позволяет немного изменить поведение существующего
класса, переопределив некоторые методы базового, наследую, тем не менее,
структуру базового.
Синтаксис наследования имеет следующий вид:
class Base
{
//…
};
class Derived: ключ_доступа Base[, ключ_доступа Base2, …]
{
//…
};
Ключи доступа
При наследовании ключ доступа определяет уровень доступа к элементам
базового класса внутри производного.
Таблица 4
![Лабораторное занятие №4. Наследование классов.](http://codingrus.ru/images/c/013.JPG)
Простое наследование
Простым наследованием называется случай, когда производный класс имеет
только один базовый класс.
Пример №1. Простое наследование.
class CShape // Определение класса «фигура»
{
public:
void Draw(void) { cout << “This is Shape”; }
};
// Определение класса «Прямоугольник»
class CRect: public CShape
{
private:
int x, y;
int x1, y1;
public:
void Draw(void) { cout << “This is Rectangle”; }
};
Задание №1
Определите иерархию классов фигур: закрашенный треугольник, треугольник,
четырёхугольник, ромб. Найдите общие черты и выразите их в отдельных
классах. Определите в классах метод Draw (рисовать), выводящий на экран
название фигуры (см. пример №1).
23 Объектно-ориентированное программирование на С++.
Конструкторы, деструкторы и наследование
Конструкторы не наследуются! Ели конструктор базового класса требует
спецификации одного или нескольких параметров, конструктор производного
класса должен вызывать базовый конструктор, используя список инициализации
элементов.
class Base
{
public:
Base(int, char*) {};
};
class Derived: public Base
{
public:
Derived(char* str):
Base(strlen(str), str) {};
};
Деструктору производного класса, напротив, не требуется явно вызывать
деструктор базового класса. В деструкторе производного класса компилятор
автоматический генерирует вызовы базовых деструкторов.
Виртуальные функции и полиморфизм
Функция-элемент может быть объявлена как virtual. Ключевое слово virtual
предписывает компилятору генерировать некоторую дополнительную
информацию о функции, позволяя использовать полиморфное поведение
функции – различную реакцию при вызове функции с одним и тем же именем
через указатель на один из объектов класса иерархии (общая для классов
функция должна быть, конечно же, определена). На практике это можно
выразить возможностью операции присваивания объекту базового класса
ссылки на производный класс.
Пример №2. Определение виртуального метода
class CShape // Определение класса «фигура»
{
public:
virtual void Draw(void) { cout << “This is Shape”; }
};
// Определение класса «Прямоугольник»
class CRect: public CShape
{
private:
int x, y;
int x1, y1;
public:
virtual void Draw(void) { cout << “This is Rectangle”; }
};
void main()
{
CShape* pShape = new CRect; //т.е. Base = Derived !
pShape->Draw();
delete pShape;
}
В результате выполнения примера №2 на экране отобразится строка «This is
Rectangle», хотя вызов метода Draw() был произведён через указатель на объект
класса CShape. Встретив виртуальную функцию, компилятор сгенерировал код
таким образом, что бы во время выполнения программы, можно было точно
определить: какую в действительности функцию нужно вызвать. Полиморфный
механизм поведения функций выключен при использовании статических
(объявленных без ключевого слова virtual) методов. В этом случае вызывается
метод класса, указатель на объект которого объявлен в программе. Если метод
Draw() не объявлять виртуальным на экран вывелась бы строка «This is Shape».
Использование механизма наследования и полиморфного поведения классов
накладывает определённые правила на определение деструктора класса. Всегда
объявляйте его виртуальным в базовых классах, что позволит вызвать
правильный деструктор вне зависимости от того, на объект какого класса в
действительности ссылается указатель.
25 Объектно-ориентированное программирование на С++.
Полиморфного поведения функций можно добиться только при
динамическом создании объектов класса!
Задание №2
Модифицируйте метод Draw() в объявленной иерархии классов для поддержки
полиморфного поведения. Определите внешнюю процедуру (не принадлежащую
ни одному классу) DrawShape, которая принимает указатель на базовый для
всей иерархии класс и вызывает метод Draw в переданном указателе на
объект.
Абстрактные классы и чистые виртуальные функции
Абстрактный класс является классом, который может использоваться только в
качестве базового для других классов. Абстрактный класс содержит одну или
несколько чистых виртуальных функций (ЧВФ). ЧВФ определена, но не имеет
реализации и предполагается, что она будет реализована в производных
классах.
К абстрактным классам применимы следующие правила:
• Абстрактный класс не может использоваться в качестве типа аргумента
функции или типа возвращаемого значения.
• Абстрактный класс нельзя использовать в явном преобразовании.
• Нельзя определить представитель абстрактного класса
(локальную/глобальную переменную или элемент данных).
• Можно определять указатель или ссылку на абстрактный класс.
• Если класс, производный от абстрактного, не реализует все ЧВФ
абстрактного класса, он тоже является абстрактным.
Абстрактные классы удобно использовать, если класс по своей сути является
моделью (абстракцией) какого-либо понятия, не существующего в реальном
мире. Как понятие «фигура», которую нельзя увидеть (даже представить) или
сказать о её свойствах, до тех пор, пока нам не укажут на конкретную фигуру,
например треугольник. Однако мы можем сказать, что практический любую
фигуру можно нарисовать (Draw), возможно подсчитать её площадь (Square)
или объём (Volume) для 3D-фигур, опять же не зная как, не имея указания на
конкретную фигуру. Эти методы (функции) и будут определены как ЧВ.
Для определения ЧВФ надо объявить её виртуальной, добавив чистый
спецификатор =0 после определения функции:
virtual <Определение функции> =0 ;
26 Объектно-ориентированное программирование на С++.
Пример №3. Определение ЧВФ
class CShape // Определение абстрактного класса «фигура»
{
public:
virtual void Draw(void)=0;
};
void main()
{
CShape* pShape; // Правильное определение
CShape* pNewShape = new CShape; // Ошибка!
CShape Shape; // Ошибка!
CShape* pSh = new CRect; // Правильное определение, если
// CRect не абстрактный
}
Задание №3
Выделите, среди классов в иерархии, кандидатов в абстрактные классы.
Перепишите их, определив нужные ЧВФ.
Задание №4
На складе хранятся химические вещества различного вида: жидкости,
порошковые, газообразные. Все препараты располагаются на стеллажах и
характеризуются номером стеллажа и позицией. Не все товары совместимы в
пределах одного стеллажа: например, жидкости и порошковые нельзя
размещать на одном стеллаже. Разработайте иерархию классов для
системы учёта препаратов на складе. Предусмотрите методы перемещения
препаратов внутри склада с учётом указанного ограничения.
Задание №5
Автозаправочная станция торгует бензином марок: «АИ-80», «АИ-92» и
дизельным топливом («ДТ»). Всё топливо хранится в цистернах разного
объёма. При покупке клиент называет марку и объём (в литрах) топлива и
расплачивается. При этом система должна проверить возможность
удовлетворения запроса: достаточный объём топлива в хранилище и
достаточность суммы покупателя. Если условия соблюдаются, система
уменьшает запас топлива в одной из цистерн и фиксирует акт продажи:
марку проданного топлива, объём, стоимость, уплаченную покупателем
сумму, сдачу и дату/время продажи.
|