Наследование. Основные понятия
Наследование. Основные понятия.
Начиная рассматривать вопросы наследования, нужно отметить, что любой объект призван собрать вместе свойства и поведение некоторого фрагмента решаемой задачи, связывая в единое целое данные и методы, относящиеся к этому фрагменту.
Вспомним. Каждый объект является конкретным представителем класса. Объекты одного класса имеют разные имена, но одинаковые по типам и внутренним именам данные. Объектам одного класса для обработки своих данных доступны одинаковые функции класса и одинаковые операции, настроенные на работу с объектами класса. Таким образом, класс выступает в роли типа, позволяющего вводить нужное количество объектов, имена (названия) которых программист выбирает по своему усмотрению.
Однако, объекты разных классов и сами классы могут находиться в отношении наследования, при котором формируется иерархия объектов, соответствующая заранее предусмотренной иерархии классов.
Иерархия классов позволяет определять новые классы на основе уже имеющихся. Имеющиеся классы обычно называют базовыми (родительскими). А, новые классы, формируемые на основе базовых - производными (классами-потомками,дочерними классами).
Производные классы получают по наследству данные и методы своих базовых классов и, конечно же, могут иметь собственные составляющие. При этом, наследуемые члены не описываются в потомке, а остаются только в базовых классах.
При наследовании некоторые имена методов данных базового класса могут быть по-новому определены в производном классе. В этом случае соответствующие компоненты базового класса становятся недоступными из производного класса. Для доступа из производного класса к компонентам базового класса, имена которых повторно определены в производном, используется операция '::' указания (уточнения) области видимости.
Любой производный класс может, в свою очередь, становиться базовым для других классов, и таким образом формируется иерархия классов и объектов. В иерархии производный объект наследует разрешенные для наследования члены всех базовых объектов. Другими словами, у объекта имеется возможность доступа к данным и методам всех своих базовых классов.
Тот тип наследования, о котором мы говорим, называется - одиночное наследование. Однако, в языке С++ допускается, еще один вид - множественное наследование. Множественное наследование - возможность класса наследовать члены нескольких никак не связанных между собой базовых классов. Об этом типе мы поговорим подробнее чуть позже.
Условный пример на наследование.
Давайте, для начала рассмотрим пример наследования условно, что называется, "на словах".
Итак, пусть есть класс "точка (позиция) на экране". Будем считать базовым классом, и на его основе построим класс "окно на экране". Данными этого класса будут унаследованные от базового класса координаты точки и две собственные переменные - ширина и высота окна.
Рассмотрим предполагаемые методы потомка, свои и полученные в наследство:
- Функция смещения окна вдоль оси X на DX. СВОЯ
- Функция смещения окна вдоль оси Y на DY. СВОЯ
- Функция получения значения координаты X левого верхнего угла. РОДИТЕЛЬСКАЯ
- Функция получения значения координаты Y левого верхнего угла. РОДИТЕЛЬСКАЯ
- Функция получения размера окна вдоль оси X (ширины). СВОЯ
- Функция получения размера окна вдоль оси Y (высоты). СВОЯ
- Конструктор - создает окно на экране с заданным именем по параметрам, определяющим левый верхний угол окна и его размеры. СВОЙ
- Деструктор - уничтожает окно с заданным именем. СВОЙ
Просто, не правда ли? Но, пока это лишь теория, а нам пора переходить к практике. Давайте только напоследок, дадим чёткое определение тому, что мы изучаем. Итак:
Наследование - это механизм, посредством которого, один объект может получать свойства другого объекта и добавлять к ним черты, характерные только для него.
Спецификаторы доступа при наследовании и синтаксис организации наследования.
Спецификаторы доступа.
При наследовании классов важную роль играет область доступа членов класса. Для любого класса все его члены лежат в области его действия. Тем самым любая принадлежащая классу функция может использовать любые данные и вызывать любые принадлежащие классу функции. Вне класса в общем случае доступны только те его компоненты, которые имеют статус public.
Напомним, что существуют следующие спецификаторы доступа к членам класса:
- Собственные (private) - методы и данные доступны только внутри того класса, где они определены.
- Защищенные (protected) - методы и данные доступны внутри класса, в котором они определены, и дополнительно доступны во всех производных классах.
- Общедоступные (public) компоненты класса видимы из любой точки программы, т.е. являются глобальными.
Все вышеперечисленные спецификаторы доступа не только указываются для конкретных членов класса, но и могут быть использованны для определения статуса наследования. Для этого спецификатор устанавливают при описании производного класса непосредственно перед именем базового класса.
Соглашения о статусах доступа при разных сочетаниях базового и производного классов иллюстрирует следующая таблица:
Синтаксис наследования.
И, наконец, финальный пряник. А, именно, синтаксис создания наследуемого класса.
class имя_класса : спецификатор_наследования имя_базового_класса{ описание_класса; }; |
Разберём синтаксис более детально:
- имя_класса - имя нового создаваемого класса.
- спецификатор_наследования - спецификатор доступа к наследуемым членам.
- имя_базового_класса - класс, от которого, необходимо отнаследоваться.
- описание_класса - тело нового класса.
Кое-что о конструкторах и деструкторах....
Здесь мы приведем несколько утверждений, связанных с работой конструкторов и деструкторов при наследовании.
Конструктор базового класса всегда вызывается и выполняется до конструктора производного класса.
Деструкторы базовых классов выполняются в порядке, обратном перечислению классов в определении производного класса. Таким образом порядок уничтожения объекта противоположен по отношению к порядку его конструирования.
Вызовы деструкторов для объектов класса и для базовых классов выполняются неявно и, почти никогда, не требуют никаких действий программиста.
Ну что ж, багаж знаний упакован - пора попрактиковаться.)
Пример реализации одиночного наследования.
А, сейчас реализуем ту конструкцию, которую мы создали условно, в одном из предыдущих разделов.
#include <iostream> using namespace std; // Класс "точка" class Point{ protected: int x; int y; public: Point(){ x=0; y=0; } // получение x int&GetX(){ return x; } // получение y int&GetY(){ return y; } }; class MyWindow: public Point{ int width; int height; public: MyWindow(int W,int H){ width=W; height=H; } // получение ширины int&GetWidth(){ return width; } // получение высоты int&GetHeight(){ return height; } // функции сдвига void MoveX(int DX){ x+=DX; } void MoveY(int DY){ y=DY; } // показ на экран void Show(){ cout<<"--------------\n\n"; cout<<"X = "<<x<<"\n\n"; cout<<"Y = "<<y<<"\n\n"; cout<<"W = "<<width<<"\n\n"; cout<<"H = "<<height<<"\n\n"; cout<<"--------------\n\n"; } }; void main(){ // создание объекта MyWindow A(10,10); A.Show(); // изменение параметров A.GetX()=5; A.GetY()=3; A.GetWidth()=40; A.GetHeight()=50; A.Show(); // сдвиг "окна" A.MoveX(2); A.MoveY(7); A.Show(); } |
Множественное наследование.
В C++ производный класс может быть порождён из любого числа непосредственных базовых классов. Наличие у производного класса более чем одного непосредственного базового класса называется множественным наследованием. Синтаксически множественное наследование отличается от единичного наследования списком баз, состоящим более чем из одного элемента. Например, так:
class A { // описание класса А }; class B { // описание класса B }; class C : public A, public B { // описание класса C }; |
Несколько особенностей множественного наследования.
При создании объектов-представителей производного класса, порядок расположения непосредственных базовых классов в списке баз определяет очерёдность вызова конструкторов умолчания. Этот порядок влияет и на очерёдность вызова деструкторов при уничтожении этих объектов.
Более существенным является ограничение, согласно которому одно и то же имя класса не может входить более одного раза в список баз при объявлении производного класса. Это означает, что в наборе непосредственных базовых классов, которые участвуют в формировании производного класса не должно встречаться повторяющихся элементов.
А, теперь, чтобы долго не задерживаться на теории, давайте расмотрим практический пример, описанный в следующем разделе урока.
Пример использования множественного наследования.
Классический пример, переходящий из поколения в поколение. Создадим примитивную структуру наследования. Соберем животное - "лось"))).
#include <iostream> #include <string.h> using namespace std; // Класс "рога" class Roga{
protected: char color[25]; int wes; public: Roga(){ strcpy(color,"Dirty"); wes=20; } Roga(char *c,int w){ strcpy(color,c); wes=w; } }; // Класс "копыта" class Kopyta{
protected: char forma[25]; int razmer; public: Kopyta(){ strcpy(forma,"Big"); razmer=10; } Kopyta(char *c,int w){ strcpy(forma,c); razmer=w; } }; // Класс "Лось", производный от // классов "рога" и "копыта" class Los:public Roga,public Kopyta{
public: char name[255];
Los(char *c){ strcpy(name,c); } // Функция потомка может обращаться к // элементам обоих базовых классов void DisplayInfo(){
cout<<"Name "<<name<<"\n"; cout<<"Forma "<<forma<<"\n"; cout<<"Color "<<color<<"\n"; cout<<"Wes rogov "<<wes<<"\n"; cout<<"Razmer kopyt "<<razmer<<"\n";
} }; void main() { //создание объекта класса-потомка Los l("Vasya"); l.DisplayInfo(); } |
Обсуждение плюсов и минусов наследования.
Итак, сегодня мы с вами рассмотрели несколько способов взаимодействия классов между собой. У этих способов есть свои достоинства и недостатки. Разберем их:
Наследование определяется статически на этапе компиляции, его проще использовать, поскольку оно напрямую поддерживается языком программирования.
В случае наследования упрощается также задача модификации существующей реализации. Если потомок замещает лишь некоторые операции, то могут оказаться затронутыми и остальные операции, т.к. возможно они вызывают замещенные.
Но у наследования есть и минусы:
Во-первых, нельзя изменить унаследованную реализацию во время выполнения.
Во-вторых, родительский класс нередко хотя бы частично определяет физическое представление своих подклассов. Реализации родительского и производного классов сильно связаны.
Композиция объектов определяется динамически во время выполнения за счет того, что объекты получают ссылки на другие объекты. Композицию можно применить, если объекты соблюдают интерфейсы друг друга. Во время выполнения программы любой объект можно заменить другим, лишь бы он имел тот же тип.
Более того, поскольку при реализации объекта кодируются прежде всего его интерфейсы, то зависимости от реализации резко снижается.
Ну, а выбор механизма, как всегда зависит от поставленной задачи.
Домашнее задание
- Создайте класс Student, который будет содержать информацию о студенте. С помощью механизма наследования, реализуйте класс Aspirant (аспирант - студент, который готовится к защите кандидатской работы) производный от Student.
- Создайте класс Passport (паспорт), который будет содержать паспортную информацию о гражданине Украниы. С помощью механизма наследования, реализуйте класс ForeignPassport (загран.паспорт) производный от Passport. Напомним, что загран.паспорт содержит помимо паспортных данных, также данные о визах, номер загран.паспорта.
- Используя понятие множественного наследования, разработайте класс "Окружность, вписанная в квадрат".