Перейти к содержимому
Теория · Классы и ООП · урок 7 из 13

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

Птица — тоже животное: у неё есть всё, что есть у любого животного, и кое-что своё. Если описать Bird отдельным классом, поля животного придётся повторить. Вместо этого Bird объявляют производным от Animal:

C++
#include <iostream>

struct Animal {
    int legs = 4;
};

struct Bird : public Animal {
    bool flies = true;
};

int main() {
    Bird b;
    b.legs = 2;
    std::cout << b.legs << ' ' << b.flies << '\n';  // 2 1
}

Базовый и производный класс

Здесь Animal — базовый класс (base class), а Bird — производный класс (derived class). Объект Bird содержит внутри целый объект Animal, поэтому у b есть и поле legs, и собственное поле flies. Поля и методы базового класса переходят к производному, а конструкторы — нет. Если у Animal есть конструктор Animal(int legs), производный класс вызывает его в своём списке инициализации: Bird() : Animal(2) {}.

Слово public перед Animal сохраняет доступ: открытые поля и методы базового класса остаются открытыми и у производного. Закрытые поля базового класса тоже лежат в объекте, но методы производного класса обращаться к ним не могут. Для полей, которые нужны производным классам, но закрыты от остального кода, есть метка protected:. Такое поле Animal может менять метод класса Bird, но не функция main.

Ссылки, указатели и копии

Производный объект можно передать туда, где ждут базовый: ссылка Bird& неявно преобразуется в Animal&, а указатель (pointer) Bird* — в Animal*. Поэтому одна функция с параметром const Animal& подходит для любого класса, производного от Animal. Через такую ссылку или указатель доступны только поля и методы Animal. К полю по указателю обращаются через стрелку: ptr->legs — то же, что (*ptr).legs.

С копией всё иначе. Если записать производный объект в переменную базового типа, создаётся новый объект Animal, и в него копируется только часть Animal. Это срезка (object slicing): поле flies в копию не попадает, а сама копия живёт отдельно от b.

C++
Bird b;
Animal& ref = b;
Animal copy = b;
b.legs = 2;
std::cout << ref.legs << ' ' << copy.legs << '\n';  // 2 4

ref обозначает сам объект b и видит новое значение, а copy сохранила прежнее. Строка copy.flies не скомпилируется: у объекта Animal такого поля нет.

Частая ошибка

Производный класс объявили через class и не написали public:

C++
class Animal {
public:
    int legs = 4;
};

class Bird : Animal {};

int main() {
    Bird b;
    return b.legs;  // ✗
}
TEXT
error: 'legs' is a private member of 'Animal'

У class наследование (inheritance) по умолчанию закрытое: открытые поля Animal становятся закрытыми в Bird. Напишите class Bird : public Animal. У struct наследование по умолчанию открытое, но public пишут и там, чтобы не гадать.