Наследование
Птица — тоже животное: у неё есть всё, что есть у любого животного, и кое-что своё. Если описать Bird отдельным классом, поля животного придётся повторить. Вместо этого Bird объявляют производным от Animal:
#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.
Bird b;
Animal& ref = b;
Animal copy = b;
b.legs = 2;
std::cout << ref.legs << ' ' << copy.legs << '\n'; // 2 4ref обозначает сам объект b и видит новое значение, а copy сохранила прежнее. Строка copy.flies не скомпилируется: у объекта Animal такого поля нет.
Частая ошибка
Производный класс объявили через class и не написали public:
class Animal {
public:
int legs = 4;
};
class Bird : Animal {};
int main() {
Bird b;
return b.legs; // ✗
}error: 'legs' is a private member of 'Animal'У class наследование (inheritance) по умолчанию закрытое: открытые поля Animal становятся закрытыми в Bird. Напишите class Bird : public Animal. У struct наследование по умолчанию открытое, но public пишут и там, чтобы не гадать.