12.5  Наследование

Наследование – это способность брать существующий – базовый класс и порождать из него новый класс – потомок с наследованием всех его атрибутов и поведения. Это, пожалуй, самая впечатляющая возможность объектно-ориентированного программирования и, возможно, единственное коренное отличие языка С++ от языка С. Наследование может использоваться множество раз при порождении объектов.

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

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

Наследование экономит массу времени на изучение свойств всех классов. Наследование позволяет на хорошем уровне разделять код и данные, а также разрешает полиморфизм. Другое достоинство наследования заключается в модульности классов. Можно распространять классы в объектном виде среди других программистов. На их базе они смогут порождать новые, специализированные классы без знания исходного текста. Уже появилось большое количество библиотек классов различных фирм. Возможно, они будут вам весьма полезны и обеспечат значительный успех в программных проектах.

Наследование позволяет повторно использовать существующие исходные тексты программ, подправлять их и перекомпилировать. Эта способность готового к компиляции исходного текста названа расширяемостью. Успешное создание потомков позволяет увеличивать программу за счет накопления уже готовых текстов программ.

Примечание: порожденный класс может только наращивать базовый класс, но не способен удалять какие-либо его элементы.

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

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

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

class имя_потомка : тип_доступа_при_объявлении_потомка  имя_базового_класса

{

// тело потомка

}

В таблице 12.2 показана доступность элементов базового класса из его потомка (в зависимости от типа доступа при объявлении производного класса).

Таблица 12.2 Доступность элементов базового класса из его потомка в зависимости от типа доступа при объявлении производного класса

Тип доступа

Доступность элемента

базового класса

в производном классе

в базовом классе

при объявлении

производного класса

Закрытый

Закрытый

Недоступен

Защищенный

Закрытый

Закрытый

Общедоступный

Закрытый

Закрытый

Закрытый

Общедоступный

Недоступен

Защищенный

Общедоступный

Защищенный

Общедоступный

Общедоступный

Общедоступный

Важно отметить, что программисты, создавая классы, создают, по сути, абстрактную основу – шаблоны для создания объектов. Из шаблона, когда нужно, создается объект, который и используется. Но прежде чем написать хотя бы одну строку исходного кода программы на языке С++, необходимо хорошо продумать необходимые классы и уровни их использования. Не существует "идеальной" иерархии классов для каждой конкретной программы. По мере продвижения разработки может оказаться, что требуется ввести новые классы, которые коренным образом изменят всю иерархию классов. Каждая иерархия классов представляет собой сплав экспериментальных исследований и интуиции, основанной на практике.

Таким образом, умелое использование наследования позволяет с небольшими усилиями модифицировать огромные по объему программы. К тому же необходимо помнить, что поставщики, число которых постоянно растёт, предоставляют пользователям объектно-ориентированные библиотеки разнообразных классов, поэтому прежде чем «изобретать велосипед»", нужно изучить возможности таких библиотек.

Пример 4

Создадим базовый класс A, который хранит координаты окна:

class A

{

public:// вместо public должно быть использовано protected:

int Left,

int Top;

int Right;

int Bottom;

public:

A ()  // Конструктор по умолчанию

{

Left = Top = Right = Bottom = 0;

}

A (int L, int T, int R, int B) // Инициализирующий конструктор

{

Left=L;

Top =T;

Right =R;

Bottom =B;

}

};

Создадим производный класс B, в котором тело будет иметь только два конструктора:

class B: public A

{

public:

B(int L,int T,int R,int B):A(L,T,R,B){}

B():A(){}

};

Несмотря на то, что в теле класса B нет членов-элементов, объект, созданный на его основе, может работать с данными базового класса. Например:

int main(int argc, char* argv[ ])

{

B C1,C2(1,2,3,4);

cout<<"Left="<<C1.Left<<" Top="<<C1.Top<<" Right="

<<C1.Right<<" Bottom="<<C1.Bottom<<endl;

cout<<"Left="<<C2.Left<<" Top="<<C2.Top<<" Right="

<<C2.Right<<" Bottom="<<C2.Bottom<<endl;

return 0;

}

Рассмотрим еще один пример.

Пример 5

Пусть необходимо из базового класса  фигура (shape) создать производный класс окружность (circle).

1. Создадим сначала базовый класс:

// Ex.cpp : Defines the entry point for the console application.

//

#include "stdafx.h"

#include<iostream.h>

class shape    // Объявление базового класса фигура

{                   

public:

double x0, y0;     // Координаты расположения фигуры

shape(double x, double y); //   Конструктор инициализирующий

virtual double area(void);   //   Виртуальная функция – элемент

//   вычисления поверхности

virtual void print();       // Виртуальная функция вывода результатов      

};

2. Затем создадим производный класс от базового класса:

class circle : public shape //  Объявление производного класса окружность

public:

double radius;       //  Дополнительные данные – радиус окружности

double area(void); // Подменяет в базовом классе функцию – элемент

void print()

{cout<<"nx0="<<x0<<"  y0="<<y0<<"  radius="<<radius<<endl;}

circle(double x, double y, double r); //   Конструктор инициализирующий

};

3. Опишем тело конструктора и виртуальные функции базового класса shape:

shape::shape(double x, double y)

{

x0 = x;          // Устанавливает координаты фигуры

y0 = y;

}

void shape::print(){cout<<"nx0="<<x0<<"  y0="<<y0<<endl;}

double shape::area()

{

return 0; // Пусть исходное значение поверхности в базовом классе равно 0;

}

4. Опишем тело конструктора и виртуальную функцию производного класса circle:

circle::circle(double x, double y, double r) : shape(x,y

//   Обратите внимание как конструктор circle

//   вызывает конструктор shape, с начальной установкой.

{

radius = r;  //      Далее устанавливается радиус круга

}

double circle::area(void)

{

return 3.14159 * radius * radius;  // Функция площади круга подменяет

//  определение, сделанное в базовом классе,

//  и вычисляет π·r2.

}

int main(int argc, char* argv[ ])

{

double s;

shape s1(10,10);

s1.area();

s1.print();

circle s2(15,17,10);

s=s2.area();

s2.print();

return 0;

}