Лабораторные работы МИРЭА 2014 (1017113), страница 10
Текст из файла (страница 10)
Модификатор доступа определяет доступность элементов базового класса в классе−наследнике. Квадратные скобки не являются элементом синтаксиса, а показывают, что модификатор может отсутствовать. Этот модификатор называется модификатором наследования.
При разработке отдельных классов используются два модификатора доступа к элементам класса: public (общий) и private (личный). При наследовании применяется еще один: protected (защищенный). Защищенные элементы класса доступны только прямым наследникам и никому другому.
Возможны четыре варианта наследования.
-
Класс от класса.
-
Класс от структуры.
-
Структура от структуры.
-
Структура от класса.
Доступность элементов базового класса из классов−наследников изменяется в зависимости от модификаторов доступа в базовом классе и модификатора наследования. Каким бы ни был модификатор наследования, приватные элементы базового класса недоступны в его потомках. В табл. 1.1 приведены все варианты доступности элементов базового класса в производном классе при любых вариантах модификатора наследования.
Таблица 1.1 Доступ к элементам базового класса в классе−наследнике
Модификатор доступа в базовом классе | Модификатор наследования | Доступ в производном классе | |
struct | class | ||
public | отсутствует | public | private |
protected | отсутствует | public | private |
private | отсутствует | недоступны | недоступны |
public | public | public | public |
protected | public | protected | protected |
private | public | недоступны | недоступны |
public | protected | protected | protected |
protected | protected | protected | protected |
private | protected | недоступны | недоступны |
public | private | private | private |
protected | private | private | private |
private | private | недоступны | недоступны |
Если модификатор наследования — public, наследование называется открытым. Модификатор protected определяет защищенное наследование, а слово private означает закрытое наследование.
Конструкторы и деструкторы при наследовании
Конструкторы не наследуются — они создаются в производном классе (если не определены программистом явно). Система поступает с конструкторами следующим образом:
-
если в базовом классе нет конструкторов или есть конструктор без аргументов (или аргументы присваиваются по умолчанию), то в производном классе создание конструктора можно опустить — компилятор автоматически создаст конструктор копирования и конструктор без аргументов;
-
если в базовом классе все конструкторы с аргументами, производный класс обязан иметь конструктор, в котором явно должен быть вызван конструктор базового класса;
-
при создании объекта производного класса сначала вызывается конструктор базового класса, затем — производного. Это автоматически выполняет компиляторэ
Деструктор класса, как и конструкторы, не наследуется, а создается. С деструкторами система поступает следующим образом:
-
при отсутствии деструктора в производном классе компилятор создает деструктор но умолчанию;
-
деструктор базового класса вызывается в деструкторе производного класса автоматически независимо от того, определен он явно или создан компилятором;
-
деструкторы вызываются (для уничтожения объектов) в порядке, обратном вызову конструкторов.
-
создание и уничтожение объектов выполняется по принципу LIFO: последним создан — первым уничтожен.
Поля и методы при наследовании
Класс−потомок наследует структуру (все элементы данных) и поведение (все методы) базового класса. Класс−наследник получает в наследство все поля базового класса (хотя, если они были приватные, доступа к ним ему запрещён). Если новые поля не добавляются, размер класса−наследника совпадает с размером базового класса. Порожденный класс может добавить собственные поля:
Дополнительные поля производного класса могут совпадать и по имени, и по типу с полями базового класса — в этом случае новые поля скрывают поля базового класса, поэтому для доступа к полям базового класса в классе−наследнике необходимо использовать префикс−квалификатор базового класса.
Класс−потомок наследует все методы базового класса, кроме операции присваивания — она создается для нового класса автоматически, если не определена явно. В классе−наследнике можно определять новые методы. В новых методах разрешается вызывать любые доступные методы базового класса.
Если в классе−наследнике имя метода и его прототип совпадают с именем метода базового класса, то говорят, что метод производного класса скрывает метод базового класса. Чтобы вызвать метод родительского класса, необходимо указывать его с квалификатором класса.
Операция присваивания и принцип подстановки
Открытое наследование устанавливает между классами отношение «является»: класс−наследник является разновидностью класса−родителя. Это означает, что везде, где может быть использован объект базового класса (при присваивании, при передаче параметров и возврате результата), вместо него разрешается подставлять объект производного класса. Данное положение называется принципом подстановки и поддерживается компилятором. Этот принцип работает и для ссылок, и для указателей: вместо ссылки (указателя) на базовый класс может быть подставлена ссылка (указатель) на класс−наследник. Обратное − неверно! Например, спортсмен является человеком, но не всякий человек — спортсмен. Здесь человек — базовый класс, а спортсмен — производный.
Помимо конструкторов, не наследуются два вида функций: операция присваивания и дружественные функции. Операция присваивания, как и конструкторы с деструктором, для любого класса создается автоматически.
В теле дочерней операции присваивания выполняется вызов родительской операции в функциональной форме. Эта операция похожа на операцию преобразования объектов базового класса в производный.
В любом классе операцию присваивания можно перегрузить неоднократно. При этом разрешается, чтобы ни аргумент, ни результат не являлись определяемым классом. Единственным ограничением является видимость определения нужных классов в точке определения операции присваивания.
Дружественные функции не наследуются, поскольку не являются методами базового класса, хотя и имеют доступ к внутренней структуре класса. При открытом наследовании можно не дублировать дружественные функции для производного класса, так как принцип подстановки обеспечивает помещение аргументов производного класса на место параметров базового класса.
Если в производном классе определены дополнительные поля, то при подстановке объектов происходит срезка (расщепление): объекту базового класса присваиваются только унаследованные производным классом поля. При передаче параметра по значению может произойти срезка при копировании. При передаче параметра по ссылке (или по указателю) срезки не происходит.
Закрытое наследование
Закрытое наследование — это наследование реализации (в отличие от наследования интерфейса): класс реализован посредством класса. Оно принципиально отличается от открытого: принцип подстановки не соблюдается. Это означает, что нельзя присвоить (во всяком случае, без явного преобразования типа) объект производного класса базовому. Поэтому закрытое наследование хорошо применять в тех случаях, когда требуется иметь функциональность базового класса, но не нужны ни копирование, ни присваивание.
При закрытом наследовании все элементы класса−наследника становятся приватными и недоступными программе−клиенту.
Можно открыть методы базового класса с помощью using−объявления, которое имеет следующий синтаксис:
using <имя базового класса>::<имя в базовой классе>;
Таким образом, закрытое наследование позволяет ограничить предоставляемую производным классам функциональность.
Виртуальные функции
Связывание это сопоставление вызова функции с телом функции. Для обычных функций−член связывание выполняется на этапе трансляции до запуска программы. Такое связывание называют «ранним», или статическим. При наследовании обычной функции−член его поведение не меняется в наследнике. Однако бывает необходимо, чтобы поведение некоторых функций−член базового класса и классов−наследников отличались. Чтобы добиться разного поведения в зависимости от типа, необходимо объявить функцию−метод виртуальной; в C++ это делается с помощью ключевого слова virtual. Виртуальные функции в совокупности с принципом подстановки обеспечивают механизм «позднего» (отложенного) или динамического связывания, которое работает во время выполнения программы.
Класс, в котором определены виртуальные функции (хотя бы одна), называется полиморфным классом.
Ключевое слово virtual можно писать только в базовом классе — это достаточно сделать в объявлении функции. Даже если определение писать без слова virtual, функция все равно будет считаться виртуальной. Правила описания и использования виртуальных функций−методов следующие.
-
Виртуальная функция может быть только методом класса.
-
Любую перегружаемую операцию−метод класса можно сделать виртуальной, например операцию присваивания или операцию преобразования типа.
-
Виртуальная функция наследуется.
-
Виртуальная функция может быть константной.
-
Если в базовом классе определена виртуальная функция, то функция−член производного класса с таким же именем и прототипом (включая и тип возвращаемого значения, и константность функции−член) автоматически является виртуальным (слово virtual указывать необязательно) и замещает функцию−член базового класса.
-
Статические методы не могут быть виртуальными.
-
Конструкторы не могут быть виртуальными.
Деструкторы могут (чаще — должны) быть виртуальными — это гарантирует корректное освобождение памяти через указатель базового класса.
Внутри конструкторов и деструкторов динамическое связывание не работает, хотя вызов виртуальных функций не запрещен. В конструкторах и деструкторах всегда вызывается «родная» функция класса.
Виртуальные функции−член можно перегружать и переопределять (в наследниках) с другим списком аргументов. Если виртуальная функция переопределена с другим списком аргументов, она замещает (скрывает) родительские методы. Константный метод считается отличным от неконстантного метода с таким же прототипом.
Родительские методы можно сделать доступными в классе−наследнике при помощи using−объявления.
Разрешается при переопределении виртуальной функции изменить только тип возвращаемого значения, если это указатель или ссылка.
Чистые виртуальные функции и абстрактные классы
Виртуальная функция, не имеющая определения тела, называется чистой (риге) и объявляется следующим образом:
virtual тип имя(параметры) = 0;
Предполагается, что данная функция будет реализована в классах−наследниках.
Класс, в котором есть хоть одна чистая виртуальная функция, называется абстрактным классом. Объекты абстрактного класса создавать запрещено. И при передаче параметра в функцию невозможно передать объект абстрактного класса по значению. Однако указатели (и ссылки) определять можно.
При наследовании абстрактность сохраняется: если класс−наследник не реализует унаследованную чистую виртуальную функцию, то он тоже является абстрактным. В C++ абстрактный класс определяет понятие интерфейса. Наследование от абстрактного класса — это наследование интерфейса.
Пример_1:
#include <iostream.h>
#include <conio.h>
class A
{
protected: