Наследование (часть первая).

Содержание статьи:

Вступление.
Лирика.
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. Перегрузка операторов.
© 2004-2005 Savardge.ru
Hosted by uCoz