Наследование классов в C++: что это и как он работает

Всем привет! Продолжаем изучать классы в C++. Сейчас поговорим об одном из свойств объектно ориентированного программирования - наследование.
Что такое наследование
Это принцип создание класса на базе уже существующего, при этом у нас есть возможность пользоваться функционалом (свойствами и методами) базового. Классы созданные таким образом называются производными или дочерними, а на базе которого создаются - родителем или базовым.
Этот механизм в объектно ориентированном программировании очень сильная фича. Она в несколько раз экономит время на создание проекта, а также не нагружает его повторяющимся кодом.
Производный класс мы можем усовершен ствовать, добавляя:
- Новые переменные.
- Функции.
- Конструкторы.
И все это не изменяя базовый класс.

Например, на базе класса про животного можно создать потомка про собаку.
Модификатор доступа protected
Для начала нужно знать об доступе protected, который является неотъемлемой частью наследования. protected - это модификатор доступа, который работает как private, но распространяется также на свойства предка. На рисунки ниже можно увидеть какими доступами можно пользоваться.

class Animals {
protected:
int zebras;
};
class Dog : public Animals {
int counter_zebras () {
return zebras;
}
};
Если бы переменная zebras
находилась в доступе private, то использование ее в функции counter_zebras
привило бы к ошибке.
Как создать дочерний класс
Чтобы наследовать класс нужно использовать конструкцию ниже:
class <имя потомка> : <модификатор наследования> <имя родительского класса>{};
Первое на что надо обратить внимание это на двоеточие (:
) оно одинарное, а не двойное как у области видимости.
Второе это <модификатор наследование>. При его оперировании можно задать какими модификаторами доступа родительского класса можно будет пользоваться в дочернем. Давайте поподробнее это разберем.
Вообщем можно указывать: public, private, protected. Из этих трех почти всегда используется public, но не плохо знать как работают другие.
Если вы новичок, то можете после информации про public перейти дальше.
public - использовать можно public
и protected
родительского класса. Кстати на рисунке выше изображены модификаторы доступа при использовании public.
class Animals {
public:
int counter; // общее кол животных
protected:
int zebras;
int bears;
int dogs;
// функция вычисление общего количества животных
count_animals() {
counter = dogs + bears + zebras;
}
set_dogs(int count_of_dogs) {
dogs = count_of_dogs;
}
};
class Dog : public Animals {
public:
int count_dogs() {
return dogs; // использовали переменную dog
}
};
- В строке 20: объявили функцию
public: count_dogs()
, которая возвращает переменнуюprivate: dogs
изanimals
.
private - пользоваться можно лишь свойствами (не функциями) родителя. Чтобы использовать функции нужно разрешить это напрямую (и без круглых скобок, только имя), а также разрешать нужно в публичном доступе (public). Делается это так <родительский класс> :: <свойства>;
.
class Dog : private Animals {
public:
int count_dogs() {
return dogs; // использовали переменную dog
}
Animals :: set_dogs;
};
int main() {
Dog jack;
int k;
cout << "Введите количество собак: "; cin >> k;
jack.set_dogs(k);
cout << "Количество собак равняется: "<< jack.count_dogs();
return 0;
}
- В строке 6: получили доступ к функции set_dogs().
- В строке 15 - 16: отсылаем количество собак и потом их выводим.
protected - идентичен private, но свойства public переходит в доступ protected.
Как себя ведут модификаторы доступа при разных модификаторах наследования:
- Модификатор наследования public: public -> public, private -> public, protected -> protected
- Модификатор наследования private: public -> нет доступа, private -> нет доступа, protected -> нет доступа
- Модификатор наследования protected: public -> protected, private -> protected, protected -> protected
Наследование конструктора
Наследованные конструкторы будут вызываться в порядке их наследования. Например, создали класс Earth, от него Animal, а от Animal создали Men. То вызов будет производиться так:
- Earth
- Animal
- Men
Для наследования конструктора нужно использовать следующую конструкцию:
<имя класса> (<имя переменных конструктора>) :
<родительский класс> (<переменные конструктора>) { <тело> };
- В начале указываем имя дочернего класса -
<имя класса>
. - Далее
<имя переменных конструктора>
указываем столько имен переменных сколько требует этого родительский конструктор, дальше передаем базовому конструктору в скобках(<переменные конструктора>)
объявленные переменные. <тело>
- это тело конструктора, об этом ниже.
Bear (name) :
Animal (string name) {};
Но чтобы все это работало, в конструкторе базового класса к переменным нужно обращаться через this->
.
class Animal {
public:
animal () {
cout << "Создан класс без первичных объвлений";
}
animal (int counter) {
this->count_of_animal = counter;
}
protected:
int counter;
int count_of_animal;
};
class Dog : Animal {
public:
dog () : animal () {} // перегрузка
dog (int counter) : // конструкторов
animal (counter) {}
get_count_animal() {
return count_of_animal;
}
};
В строках 16 - 18: мы наследовали два конструктора, которые можем перегружать.
int main () {
Dog march;
Dog april(12);
}
Если хотите подробнее познакомится c перегрузками (функций) переходите сюда.
Но может появится необходимость добавить еще одну дополнительную переменную в конструктор, которой не имеет базовый. Для этого объявляем еще одну переменную в <переменные конструктора>
и делаем с ней, все что угодно в <теле>
.
class Dog : Animal {
public:
dog (int counter, vector names_dogs) :
animal (counter) {
for (int i = 0; i < names_dogs.size(); i++) {
names.push_back(names_dogs[i]);
}
}
get_names () {
for (int i = 0; i < names.size(); i++) {
cout << names[i] << ", ";
}
}
// ...
private:
vector names;
};
- В строке 3: добавили вектор в конструктор. В этом векторе должны храниться имена собак.
- В строке 16: объявили вектор names в котором должны хранится клички собак.
- В строках 5 - 7: считываем имена в вектор.
Давайте посмотрим, как это работает на реальном примере:
int main () {
setlocale(LC_ALL, "Rus");
int n;
cout << "Введите количество собак "; cin >> n;
vector <int> vec;
string s;
for (int i = 0; i < n; i++) {
cout << "Введите имя " << i + 1 << " собаки: "; cin >> s;
vec.push_back(s);
}
cout << endl;
Dog jule(n, vec);
jule.get_names();
return 0;
}
- В строке 5: предлагаем пользователю ввести количество собак.
- В строке 7: создали вектор чисел
vec
. - В строке 16: объявили класс
jule
сразу же передав число собак и вектор и имен. - В строке 17: выводим все имена.
Введите количество собак: 3
Имя первой 1 собаки: Jack
Имя первой 2 собаки: Rex
Имя первой 3 собаки: Abbey
Jack, Rex, Abbey
Process returned 0 (0x0) execution time : 0.010 s
Press any key to continue.
Наследование деструктора
Наследованные деструкторы вызываются наоборот по сравнению с вызыванием конструктора. Если классы созданные так Earth -> Animal -> Men, то цепочка вызовов конструктора имеет такой вид:
- Men
- Animal
- Earth
Про деструктор можно почитать здесь.
class name {
private:
~name () {
cout << "1";
}
};
class second_name : public name {
public:
~second_name () {
cout << "2";
};
};
На этом все! Если есть вопросы, то задавайте их в комментариях ниже. Удачи!
Обсуждение