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

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

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

код на языке c++
class infantry
{
private:
  int health;

public:
  int 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:

код на языке c++
class infantry
{
protected:
  int health;

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

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

код на языке c++
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.

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

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

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

код на языке c++
class lorry
{
public:
  int fuel;
  lorry(): fuel(100)
  {}
  lorry(int f) : fuel(f)
  {}
};

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

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

код на языке c++
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.

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

На сегодня всё. Упражнений не будет. Но у вас уже достаточно знаний чтобы на основе pseudo_game сделать что-нибудь интересное и одновременно применить знания, полученные при чтении данного урока.