Компания ALT Linux
Опубликован: 07.03.2015 | Доступ: свободный | Студентов: 2136 / 487 | Длительность: 24:14:00
Лекция 10:

Объектно-ориентированное программирование

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

Наследование классов позволяет строить иерархию, наверху которой находятся более общие классы, а внизу — более специализированные. Попробуем привести наглядный пример иерархии наследования. Предположим, мы создаём объектно-ориентированную систему работы с графикой, и предусмотрели класс point, описывающий отдельную двумерную точку на экране. В этом классе хранятся координаты точки, её цвет, а также методы для управления этими данными. При необходимости можно легко создать на базе класса point производный класс, хранящий трехмерную вершину (например, vertex): добавить в нём третью координату, соответствующие конструкторы, модифицировать некоторые методы. Однако не следует путать отношение наследования с отношением включения. Например, будет нелогичным строить на базе класса point или класса vertex класс region, описывающий объекты с произвольным количеством вершин: скорее, это должен быть класс-контейнер, содержащий в себе массив объектов point или vertex.

Таким образом, есть смысл создавать на базе существующего класса производный, если мы хотим получить частный случай с модифицированной функциональностью.

В C++ новый класс строится на базе уже существующего с помощью конструкции следующего вида:

class parent {.....};
class child : [ модификатор наследования ] parent {.....};

При определении класса-потомка, за его именем следует разделительдвоеточие ":", затем необязательный модификатор наследования и имя родительского класса. Модификатор наследования определяет видимость наследуемых переменных и методов для класса-потомка и его возможных потомков. Таким способом определяется, какие права доступа к переменным и методам класса-родителя будут "делегированы" классу-потомку.

При реализации наследования область видимости принадлежащих классу данных и методов можно определять выбором одного из следующих модификаторов доступа:

  • private (закрытый);
  • public (общедоступный);
  • protected (защищённый).

Эти модификаторы могут произвольно чередоваться внутри описания класса и уже использовались нами для обозначения открытых и закрытых секций класса.

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

Иными словами, одни и те же ключевые слова могут использоваться и в качестве модификаторов наследования, и в качестве модификаторов доступа:

Таблица 10.1. Разница между модификаторами наследования и доступа
Модификаторы наследования:
  • public
  • protected
  • private
Модификаторы доступа:
  • public
  • protected
  • private
class point {...};
class vertex : public point
{...};
class point
{
public :
int color;
...
};

То, как изменяется доступ к элементам базового класса из методов производного класса в зависимости от значения модификаторов наследования, показано в табл. 10.2.

Таблица 10.2. Сочетание модификаторов наследования и доступа
Модификатор доступа: Модификатор наследования:
public protected private
public public protected private
protected protected protected private
private нет доступа нет доступа нет доступа

Из таблицы видно, в производном классе доступ к элементам базового класса может быть сделан более ограниченным, но никогда нельзя сделать его менее ограниченным.

10.4.1 Конструкторы и деструкторы при наследовании

Базовый класс, производный класс или оба могут иметь конструкторы и деструкторы. Если и у базового и у производного классов есть конструкторы и деструкторы, то они срабатывают по очереди: конструкторы выполняются в порядке наследования, а деструкторы — в обратном порядке.

#include<iostream>
using namespace std;
class parent
{
public :
	parent	( ) { cout << "Работа конструктора базового класса\n "; }
	˜parent ( ) { cout << "Работа деструктора базового класса\n "; }
};
class child : public parent
{
public :
	child ( ) { cout<<"Работа конструктора производного класса\n "; }
	˜ child ( ) { cout<<"Работа деструктора производного класса\n "; }
};
main ( )
{
	child c1;
}

Выполнение этой предельно простой программы иллюстрирует порядок срабатывания конструкторов и деструкторов. Результат её работы следующим образом отображается на экране:

Работа конструктора базового класса
Работа конструктора производного класса
Работа деструктора производного класса
Работа деструктора базового класса

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

конструктор_производного_класса (список формальных параметров)
: конструктор_базового_класса (список фактических параметров)
{
	... //тело конструктора производного класса
}

Списки параметров родительского и дочернего конструкторов могут совпадать, а могут и различаться. Например, конструктор производного класса часто принимает некоторые аргументы только для того, чтобы передать их конструктору базового класса. Приведём пример с уже обсуждавшимися классами point и vertex.

#include<iostream>
using namespace std;
class point
{
protected :
	int x, y, color;
public :
	point ( int p1, int p2, int c );
};
point::point ( int p1, int p2, int c )
{
	x=p1;
	y=p2;
	color=c;
}
class vertex : public point
{
	int z;
public :
	vertex ( int p1, int p2, int p3, int c );
};
vertex::vertex ( int p1, int p2, int p3, int c ) : point ( p1, p2, c )
{
	z=p3;
}
main ( )
{
	vertex c1 ( 2, 3, 4, 0 );
}

Допускается также использовать эту краткую запись передачи параметров родительскому конструктору, чтобы компактно проинициализировать переменные дочернего класса. Например, мы могли бы написать в предыдущем примере:

vertex::vertex ( int p1, int p2, int p3 )
: point ( p1, p2 ), z ( p3 )
{
	// z=p3;
}

С наследованием конструкторов класса связана ещё одна специфическая особенность: если в базовом классе есть перегруженный оператор присваивания, он не наследуется производными классами. Если оператор присваивания был перегружен в родительском классе, а в производном — нет, то присваивание объектов производного класса не будет вызывать ошибку, однако выполняться при этом будет не перегруженный оператор, а присваивание по умолчанию, т. е. побитовое копирование свойств объекта.

Сергей Радыгин
Сергей Радыгин

Символы кириллицы выводит некорректно. Как сделать чтобы выводился читабельный текст на русском языке?

Тип приложения - не Qt,

Qt Creator 4.5.0 основан на Qt 5.10.0. Win7.

 

Юрий Герко
Юрий Герко

Кому удалось собрать пример из раздела 13.2 Компоновка (Layouts)? Если создавать проект по изложенному алгоритму, автоматически не создается  файл mainwindow.cpp. Если создавать этот файл вручную и добавлять в проект, сборка не получается - компилятор сообщает об отсутствии класса MainWindow. Как правильно выполнить пример?