Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Итог_Пособие C++.doc
Скачиваний:
8
Добавлен:
01.07.2025
Размер:
2 Мб
Скачать
☆

3.8 Наследование и полиморфизм

3.8.1. Основные понятия

Одной из главных целей ООП является многократное использование кода. Библиотеки C++, основанные на классах, предоставляют удобные механизмы достижения этой цели.

Наследование – свойство классов порождать своих потомков, которые могут наследовать данные и функции своих родителей. Из этого определения выделяются две сущности:

Класс-потомок Класс-родитель

(наследник, производный класс) (предок, базовый класс)

Класс-потомок может пользоваться многими элементами классов-родителей без их повторного определения (но не всеми – этот вопрос мы далее подробно объясним). У каждого родителя может быть сколько угодно потомков. В С++, в отличие от многих других языков программирования, разрешена и противоположная ситуация – класс-потомок может иметь более одного родителя (этот случай называется множественным наследованием, его также далее поясним).

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

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

3.8.2 Одиночное наследование

Чаще всего у класса наследника бывает всего один предок – это и есть одиночное наследование. В С++ одиночное наследование реализовано так:

Сlass|struct имя_класса : тип_наследования имя_предка

{ новые поля и методы, переопределённые методы предка };

Тип наследования влияет на область видимости элементов родительского класса в классе-потомке. Имеется три типа наследования: public, protected и private.

Запишем различные виды наследования в виде таблицы доступа к элементам в классе-потомке.

Тип наследования

public

private

protected

Доступ к элементу

public

public

private

protected

private

недоступно

недоступно

недоступно

protected

protected

private

protected

Рассмотрим типы наследования подробнее. При наследовании public открытые элементы предка остаются открытыми. При наследовании protected и private область видимости сужается, то есть если элементы были открытыми, то стали скрытыми или защищенными.

К элементам типа private в базовом классе нельзя получить доступ из класса наследника. Поэтому, если доступ к каким-то скрытым элементам базового класса потомку всё-таки нужен, их область видимости private в базовом классе надо заменить на protected.

Если тип наследования не указан, то по умолчанию он считается private для типа class, и public для struct. Можно считать это правило ещё одним отличием в типах class и struct.

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

Пример, который представлен далее, содержит определение базового класса coord, представляющего координаты точки (x и y), и его потомка point, который дополнен информацией о цвете точки (r, g, b – интенсивность красного, зелёного и синего цветов).

// Пример 3.4 реализация одиночного наследования

#include <iostream>

class coord { // класс Координаты точки - предок

protected:

int x, y;

public:

void set_xy (int _x, int _y) {x=_x; y=_y;}

void add (int dx, int dy){x += dx; y += dy;}

void show() {std::cout << x << ' ' << y << std::endl;}

};

class point : public coord { // класс Точка - потомок

protected:

int r, g, b;

public:

void set_color(int _r, int _g, int _b){r = _r; g = _g; b = _b;}

void show() {

coord::show();

std::cout << r << ' ' << g << ' ' << b << std::endl;

}

};

int main() {

point p;

p.set_xy(100, 70);

p.set_color(200, 0, 0);

p.show();

// эксперимент с указателями есть ошибка!

coord *cor = &p;

cor-> show();

point *pp = cor; //ошибка - надо point *pp = (point *)cor;

}

В функции main продемонстрировано применение указателей для объектов наших классов.

В С++ есть правило, при котором присваивать указатели и ссылки классов-потомков указателям и ссылкам классов-родителей можно без явного приведения типов. Это приведение вверх – преобразование указателя или ссылки класса-потомка к указателю или ссылке класса родителя. Почему так? Потому что все свойства базового класса у нас унаследованы, и при работе с указателем или ссылкой базового класса мы можем работать только с элементами данного класса, а класс потомок уже имеет все эти элементы, следовательно, мы можем работать с ним как с базовым классом.

Для того чтобы привести указатель базового класса к указателю на класс потомок необходимо использовать явное приведение типов. Это приведение вниз – преобразование указателя или ссылки базового класса в указатель или ссылку класса потомка.