Наследование классов в C++: что это и как он работает
![обложка статьи](/static/3d1808dd70e2df858a85b1418444c9ce/4b190/%D0%BD%D0%B0%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BD%D0%B8%D0%B5-%D0%B2-C.jpg)
Всем привет! Продолжаем изучать классы в C++. Сейчас поговорим об одном из свойств объектно ориентированного программирования - наследование.
Что такое наследование
Это принцип создание класса на базе уже существующего, при этом у нас есть возможность пользоваться функционалом (свойствами и методами) базового. Классы созданные таким образом называются производными или дочерними, а на базе которого создаются - родителем или базовым.
Этот механизм в объектно ориентированном программировании очень сильная фича. Она в несколько раз экономит время на создание проекта, а также не нагружает его повторяющимся кодом.
Производный класс мы можем усовершен ствовать, добавляя:
- Новые переменные.
- Функции.
- Конструкторы.
И все это не изменяя базовый класс.
![наследование в c++](/static/be6c2124ffe543ed0817766f28eeac6f/dba9a/%D0%BD%D0%B0%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D0%B2-c-1.png)
Например, на базе класса про животного можно создать потомка про собаку.
Модификатор доступа protected
Для начала нужно знать об доступе protected, который является неотъемлемой частью наследования. protected - это модификатор доступа, который работает как private, но распространяется также на свойства предка. На рисунки ниже можно увидеть какими доступами можно пользоваться.
![наследование модификаторов c++](/static/353360cde6ae28fd21f0ed63ddaad088/1b853/%D0%BD%D0%B0%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BC%D0%BE%D0%B4%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%82%D0%BE%D1%80%D0%BE%D0%B2-c.png)
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";
};
};
На этом все! Если есть вопросы, то задавайте их в комментариях ниже. Удачи!
Обсуждение