PDA

Просмотр полной версии : [Урок] [Цикл уроков программиста] 13. Наследование



Tracker1
30.07.2013, 20:35
В сегодняшнем уроке мы будем говорить о наследовании. Лучше всего наследование изучать на конкретных примерах.

Итак, наследование. Данный вид взаимоотношения классов применяется в разных ситуациях.

Начнём с примера:


class infantry
{
private:
int health;

public:
ind damage;

infantry() : health(100),damage(10) // конструктор по умолчанию
{}

infantry(int h, int d) : health(h), damage(d) // конструктор с двумя аргументами
{}

void set_health (int h) // записать значение в поле health
{
health = h;
}

int get_health () // получить значение из поля health
{
return health;
}
};


Здесь у нас определён класс infantry (пехота). В классе два поля: health (здоровье) и damage (урон противнику). Переменная-член health расположена в блоке private. Доступ к данному полю возможен только через функции set_health() и get_health(). Для damage тоже нужно использовать похожие функции, но для сокращения кода я просто объявил damage в блоке public.

Кроме того я добавил два конструктора: по умолчанию и с двумя аргументами.

С помощью наследования можно исправить или улучшить существующий класс.

Допустим, мы пишем игру уже довольно давно: несколько дней или даже несколько месяцев. И на каком-то этапе понадобилось включить пехотинцев с гранатами. Причём кроме гранат, данный вид пехотинцев полностью похож на объекты класса infantry. К тому же, обычно ситуация усугу***ется ещё и тем, что в классе не два поля и две функции, а с десяток и того и другого.

Единственное изменение, которое нам необходимо сделать в классе infantry: ключевое слово private заменить на protected:


class infantry
{
protected:
int health;

public:
// остальной код остался без изменений
};


Теперь создаём новый класс:


class grenadier : public infantry
{
public:
int grenades;
};


Поле grenades я объявил в блоке public, чтобы не писать функции доступа.

Данный класс обладает всеми свойствами класса infantry и вдобавок у него есть поле grenades. При этом класс infantry - базовый класс, а класс grenadier - производный.

В заголовке класса grenadier после двоеточия стоит ключевое слово public и имя базового класса infantry. public задаёт общее наследование. Существует ещё частное (private), но мы пока не будем обсуждать такие тонкости.
Спецификатор доступа protected

Спецификаторы доступа public (общий) и private (частный) мы обсуждали при изучении классов. Существует ещё один спецификатор доступа - protected (защищённый).

Спецификатор доступа protected аналогичен private, только он позволяет получить доступ к защищённым данным из производного класса. Когда в программе вы будете применять наследование, то вместо спецификатора private всегда используйте protected.

С помощью наследования удобно строить иерархию классов. При этом более общие и абстрактные классы - базовые, а более специализированные - производные.

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

Для пояснения данной темы нам понадобится ещё один класс


class lorry
{
public:
int fuel;
lorry(): fuel(100)
{}
lorry(int f) : fuel(f)
{}
};


Здесь объявлен класс lorry (грузовик) с одним полем fuel (топливо) и двумя конструкторами.

Объединив два класса infantry и lorry, мы можем получить новый класс:


class mech_inf : public infantry, public lorry
{
public:
mech_inf() : infantry(), lorry() // обратите внимание на списки инициализации
{}
mech_inf(int h, int d, int f) : infantry(h,d), lorry(f)
{}
};


В класс mech_inf (mechanized infantry - мотопехота) мы не добавили ничего нового. Мы просто в заголовке класса mech_inf указали, что он является производным от двух других классов: infantry и lorry. В классе объявляются только два конструктора. Обратите внимание, что в списке инициализации конструктора mech_inf мы вызываем конструкторы infantry() и lorry().

Теперь можно создать объект нового класса.


mech_inf division_1(100, 20, 100);


При этом он обладает свойствами двух базовых классов:


division_1.damage = 10; // damage - член класса infantry
division_1.fuel = 200; // fuel - член класса lorry


Напоследок давайте рассмотрим ещё одну ситуацию. Допустим в классах infantry и lorry есть метод с одним и тем же именем и одними и теми же аргументами. Пусть метод называется move() и в нашем примере у него не будет аргументов.


division_1.move(); // объект класса mech_inf


В данном примере объект не знает, какой из методов вызывать: принадлежащий классу infantry или классу lorry. Чтобы разрешить данную проблему необходимо использовать операцию глобального разрешения:


division_1.infantry::move();


Здесь мы вызываем метод move() объявленный в классе infantry.

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