|
Наследование
(часть первая).
Содержание статьи:
Вступление.
Лирика.
Public, private,
protected.
Виртуальные
методы.
Конструирование
и
уничтожение объектов.
Вступление.
к началу статьи
Доброго времени суток всем. Вечером трудного 12-ти часового рабочего
дня, захотелось мне чего-то элегантного и красивого… Поэтому
я и решил посвятить статью третьему киту, на котором стоит CPP
– наследованию (первые два – полиморфизм и
инкапсуляция), поскольку это ещё один эффективный (хотя и не лишенный
недостатков) способ ускорить разработку Ваших программ.
Лирика.
к началу статьи
Давайте представим такую ситуацию: Вам нужно сделать несколько схожих
по своей сути, но отличающихся реализацией, классов. Возможно даже
одинаковые фрагменты кода. Переписывать каждый раз с нуля класс как-то
грустно, поэтому Вы мудро решаете воспользоваться наследованием. Его
суть заключается в том, чтобы создать в базовом классе некоторый
наиболее общий функционал, а затем наляпать производных от него
классов, задействующие алгоритмы базового. Понятно? Нет?!? Ладно,
хватит туманных объяснений и расплывчатых формулировок – пора
переходить к реальным примерам. Вот один из них: нам надо реализовать
класс Геометрическая_Фигура. Сделаем базовым класс для работы с
произвольным многоугольником: |
class cNGon{ cVector2 v_array[100]; int v_num; public: //добавляем вершину в N-угольник
void AddVert( cVector2 );
void AddVert( float x , float y );
//вычисляем периметр
float P( void );
//вычисляем площадь
float S( void ); };
|
Хоть работать с
произвольным N-угольником
и неудобно, бросить его тоже жалко. Поэтому сделаем класс, который
будет использовать алгоритмы cNGon’а, но в то же время скроет
от нас все сложности его реализации: |
class cRect:public cNGon{ public:
void CreateRect( cVector2 v , float w , float h ){
AddVert( v );
AddVert( v.x + w , v.y );
AddVert( v.x , v.y + h );
AddVert( v.x + w , v.y + h );
}
};
|
Вот уже готов целый
класс! Формально,
новая структура данных вsглядит так: |
class cRect{
cVector2 v_array[100];
int v_num;
public:
void AddVert( cVector2 );
void AddVert( float x , float y );
float P( void );
float S( void );
void CreateRect( cVector2 v , float w , float h ){
AddVert( v );
AddVert( v.x + w , v.y );
AddVert( v.x , v.y + h );
AddVert( v.x + w , v.y + h );
}
};
|
Именно так. Со старыми
функциями ничего
не произошло. И мы сразу, без нудного переписывания кода, можем
использовать функцию P(…)! можем использовать функцию
S(…)!!! Можем использовать функцию
AddVert(…)… стоп. А AddVert зачем? Не-е-ет, так
дело не пойдет. У нас ведь прямогуольник. Зачем ему пятая вершина?
Функцию надо спрятать. Поэтому меняем public
(это называется спецификатором доступа) на private.
Тогда схема будет выглядеть так: |
class cRect{ cVector2 v_array[100]; int v_num;
void AddVert( cVector2 ); void AddVert( float x , float y );
float P( void );
float S( void ); public: void CreateRect( cVector2 v , float w , float h ){ AddVert( v ); AddVert( v.x + w , v.y ); AddVert( v.x , v.y + h ); AddVert( v.x + w , v.y + h ); } };
|
Public,
private, protected.
к началу статьи
Сейчас, пока мы не убежали далеко вперёд, по - быстрому разберёмся со
всеми спецификаторами доступа. Как Вы знаете, есть в классах секции с public
методами и полями, есть секции с private
членами. Но есть еще спецификатор private.
Если класс не наследуется, то его действие совпадает с private.
А что будет при наследовании, разберем с помощью следующей таблицы:
спецификатор
доступа к базовому классу при наследовании. |
спец.
доступа к полю в базовом классе |
доступ
к полю в производном классе |
private |
private
public
protected |
private
private
private |
public |
private
public
protected |
private
public
protected |
protected |
private
public
protected |
private
protected
protected |
Как пользоваться таблицей, рассмотрим на примере. Допустим, мы хотим
добавить класс Квадрат: |
class cNGon{ cVector2 v_array[100]; int v_num; public:
void AddVert( cVector2 ); void AddVert( float x , float y );
float P( void );
float S( void ); };
class cRect:private cNGon{ public: void CreateRect( cVector2 v , float w , float h ){ AddVert( v ); AddVert( v.x + w , v.y ); AddVert( v.x , v.y + h ); AddVert( v.x + w , v.y + h ); } };
class cSquare: private cNGon{ public: void CreateSquare( cVector2 v , float w ); };
//где-то в программе cSquare Square; Square.S();//ошибка
|
Можете быть уверены
–
компилятор этот код не пропустит. Все дело в “private
cNGon” (см. третью колонку таблицы). Дело в том, что при
наследовании с ключом private,
public
поля базового класса видны в производном классе и не видны в иерархии
ниже производного (в нашем случае это cSquare) (вот в чем ошибка), а
вот private
и protected
поля базового класса не видны даже в производном классе. Как же быть?
Ведь все затевалось ради этих алгоритмов S(…) и
P(…)? Есть выход: наследование с ключом доступа private.
Тут вот какое дело: поле базового класса, наследуемого с ключом private,
закрывается (становится private
– см. таблицу) для всех классов иерархии ниже
непосредственного производного класса. Но если базовый класс
наследуется с ключом protected,
то public/<
font color=0000ff>protected поля становятся protected
полями, то есть их видно во всех производных классах вниз по иерархии
(до тех пор пока она не упрется в private).
Отсюда вывод – если хотите организовать сквозной интерфейс
– используйте protected
наследование. В нашем случае: |
class cRect:protected cNGon{ public: void CreateRect( cVector2 v , float w , float h ){ AddVert( v ); AddVert( v.x + w , v.y ); AddVert( v.x , v.y + h ); AddVert( v.x + w , v.y + h ); } };
|
Теперь ошибки не
возникнет. В завершении
этой главы хочется сказать, что наследовать можно более чем от одного
базового класса: |
class cRect:protected cNGon, private cOutput{ public: void CreateRect( cVector2 v , float w , float h ){ AddVert( v ); AddVert( v.x + w , v.y ); AddVert( v.x , v.y + h ); AddVert( v.x + w , v.y + h ); } };
|
Виртуальные
методы.
к началу статьи
А что если мы хотим подправить код какой-либо функции базового класса,
не изменяя её списка формальных параметров? Оказывается, есть способ
сделать это (на примере предыдущих классов): |
class cNGon{ cVector2 v_array[100]; int v_num; public:
void AddVert( cVector2 ); void AddVert( float x , float y );
cVector2 GetVert( int i ){return(v_array[ i ]);}
virtual float P( void );
float S( void ); };
class cRect:protected cNGon{ public: float P( void ){ return( (GetVert(2).x-GetVert(0).x) * ( GetVert(2).y-GetVert(0).y) ); } void CreateRect( cVector2 v , float w , float h ){ AddVert( v ); AddVert( v.x + w , v.y ); AddVert( v.x , v.y + h ); AddVert( v.x + w , v.y + h ); } };
|
Конструирование
и уничтожение объектов.
к началу статьи
С вызовом конструкторов не будет никаких проблем, т.к. есть четкие
правила, что и когда должно вызываться:
1. сначала вызывается конструкторы базовых классов в порядке их
указания в списке |
class cRect:protected cNGon, private cOutput{ public: void CreateRect( cVector2 v , float w , float h ){ AddVert( v ); AddVert( v.x + w , v.y ); AddVert( v.x , v.y + h ); AddVert( v.x + w , v.y + h ); } };
|
В данном примере будет
сначала вызван
cNGon(), а затем – cOutput(). Кстати, если ключ доступа не
указывать, то по умолчанию будет считаться, что класс наследуется с
доступом private. Так следующий пример эквивалентен предыдущему: |
class cRect:protected cNGon, cOutput{ public: void CreateRect( cVector2 v , float w , float h ){ AddVert( v ); AddVert( v.x + w , v.y ); AddVert( v.x , v.y + h ); AddVert( v.x + w , v.y + h ); } };
|
2. затем будет вызываться конструкторы полей класса в порядке их
объявления |
class cOutput{ cViewPort VP; cCamera Cam; };
|
Здесь сначала будет
вызван cViewPort(), а
потом cCamera().
И в первом и во втором случае будут вызываться конструкторы по
умолчанию.
3. после всего этого выполняется конструктор самого класса.
Деструкторы же вызываются в противоположном порядке (хотя есть один
нюанс, который мы рассмотрим в следующей статье).
Первый из
двух уроков, посвященных наследованию, подошел к концу. Я специально не
стал рассказывать все сразу. Во-первых, у Вас была бы каша в голове, а
во-вторых, Вам нужно время что бы впитать все вышеперечисленное,
совершить много ошибок… тогда следующий урок Вы усвоите
почти сразу. А пока, разрешите откланяться, до встречи!
к началу
статьи
ЗЫ: вопросы,
пожелания, замечания шлите на dodonov_a_a (__AT) inbox.ru
Смежные вопросы:
Урок 1. Основы классов.
Урок 2. Конструкторы
копий, оператор присваивания.
Урок 3. Перегрузка
функций.
Урок 4. Друзья.
Урок 5. Перегрузка
операторов. |
|
|