Читайте также:
|
|
Когда производный класс Derived получен открытым наследованием, объекты класса Derived могут быть использованы как объекты базового класса:
(i) Присваивание объекта производного класса объекту базового класса
Базовая часть правой стороны оператора присваивания, т.е., объекта производного класса, присваивается объекту базового класса с левой стороны. После присваивания слева, конечно, будет объект базового класса, но не преобразованный в объект производного класса. Например, пусть класс B наследован от класса A. Следующий код присваивает объект класса B анонимной переменной типа A, которая создана в куче:
B b;
A* p = new A;
*p = b;
Тогда переменная, на которую указывает p есть объект класса A, но не класса B.
(ii) Ссылка на базовый класс, ссылающаяся на производный класс
Эта ситуация возникает, когда
Напомним, что речь идет об открытом наследовании. В упомянутых выше ситуациях, объект производного класса будет иметь два имени: одно для производного класса и другое для базового. Когда используется ссылка на базовый класс, этот объект ведет себя как если бы он был объектом базового класса. Иными словами, члены, определенные в производном классе не могут быть достигнуты посредством этой ссылки на базовый класс. В частности, если функция, определенная в базовом классе, замещена функцией, определенной в производном классе, то обращение к этой функции через ссылку на базовый класс, инициализированную объектом производного, приведет к вызову версии функции из базового класса.
Объект производного класса, переданный в функцию, которая принимает ссылку на базовый класс, или возвращает ее из функции, остается объектом производного класса, хотя ведет себя внутри функции как объект базового класса.
Так как конструктор копирования базового класса имеет параметром ссылку на базовый класс, то мы можем передать объект производного класса этому копирующему конструктору для создания объекта базового класса. Заметим, что, хотя этот параметр – объект производного класса, объект, конструируемый конструктором копирования базового класса, будет объектом базового класса. В частности, объект производного класса может быть передан по значению в функцию, которая принимает параметр базового класса. Формальный параметр создается, при этом, конструктором копирования базового класса, используя объект производного класса. И поэтому, формальный параметр – это объект базового класса, не имеющий какой бы то ни было связи с производным классом.
Аналогичная ситуация возникает и в случае, когда объект производного класса возвращается по значению из функции, имеющей тип возвращаемого значения – базовый класс. Временный объект, созданный после оператора return, есть объект базового класса, инициализированный объектом производного класса.
(iii) Указатель на базовый класс может указывать на объект производного класса
Это происходит, когда
На указатель на производный класс можно ссылаться с помощью ссылки на указатель на базовый класс (семантика таких предложений почти трансцендентальна в исполнении на великом русском языке…). Это случается, когда указатель на производный класс передается по ссылке в функцию, принимающую как параметр указатель на базовый класс. В этом случае указатель производного типа ведет себя внутри функции так, как будто он является указателем базового типа (т.е., указатель на базовый класс).
В таких случаях и объект производного класса ведет себя так, как если бы он был объектом базового класса, если к нему осуществляется доступ через указатель, который может быть указателем на базовый класс или ссылкой на такой указатель (инициализируемой указателем производного типа). Однако, сам объект, конечно, продолжает оставаться объектом производного класса.
Заметим (и напомним), что, например, в случае массивов арифметика указателей зависит от размера элементов массива. Если p – указатель на массив типа T, p + 3 представляет собой адрес, вычисленный как “значение указателя p, т.е., адрес того элемента массива, на который в текущее время указывает p, плюс 3 * sizeof(T)”. Поэтому, если арифметика указателей используется, когда указатель на базовый класс указывает на элемент массива объектов производного класса, то результат может оказаться совсем не тем, который мы ожидали. Например, если указатель p на базовый класс указывает на первый элемент массива объектов базового класса (по определению указателя), то тогда p + 1 представляет собой адрес " p + sizeof(base) ", который не будет адресом второго элемента массива объектов производного типа. В частности, при передаче массива объектов производного класса в функцию, чей параметр объявлен как массив базового класса, т.е., формальный параметр, в действительности, есть указатель на базовый класс, операция индексации будет выполняться в соответствии с размером объекта базового класса. Такие вероятные ошибки компилятор может не обнаружить.
Напомним, что мы говорили об использовании производных объектов в качестве базовых. Обратное, по смыслу, запрещено: объект базового класса не может использоваться как объект производного класса. В частности, запрещено присваивание объекта базового класса объекту производного, или присваивание указателя базового типа указателю на производный класс, или передача объекта базового класса по значению или по ссылке в функцию, принимающую (как параметр) объект производного класса.
Однако, если указатель на базовый класс указывает на объект производного класса, он может быть явно преобразован в указатель на этот производный класс и обеспечить, тем самым, доступ к дополнительным (собственным) членам производного класса. Такое преобразование типа называется нисходящим (понижающим) приведением типа (down-casting). Но такое приведение типов достаточно опасная практика в программировании, поскольку указатель на базовый класс может, в действительности, не указывать на объект того производного класса, о котором думает программист. ANSI C++ предоставляет возможность безопасного нисходящего приведения (начальные сведения о приведении типов даны в Лекции No. 4).
à(К делу почти не относится…) Заметим, что в контексте нашего обсуждения, важно сохранение истинной идентичности (тождественности) передаваемого объекта. Например, пусть функция имеет параметром тип базового класса. Если фактический параметр есть объект производного класса, тогда при передаче аргумента в функцию по значению, обеспечивающий тождественность объект есть объект базового класса, а при передаче по ссылке - объект производного класса (поскольку ссылка всегда должна указывать на конкретный объект). Хотя в обоих случаях мы говорим, что этот параметр ведет себя в функции как объект базового класса, но имеется существенное различие: мы сможем определить некоторые функции, называемые виртуальными функциями (см. ниже), и в базовом классе, и в производном, так что версии используемых функций будут зависеть от истинной идентичности объекта, а не от типа ссылки или указателя. ß
Перечислим и коротко обсудим явные преобразования типов данных, которые доступны в программах на языке C++, в частности, при работе с классами при наследовании. Операции приведения типов, унаследованные из языка C, мы здесь обсуждать не будем.
(Подробнее …. См. Т. Павловская Гл.8).
В общем, преобразования типов по назначению делятся на восходящие (повышающие), нисходящие (понижающие) и перекрестные. Если представить себе некую иерархию типов, например, в виде дерева, то повышающие преобразования соответствуют “движению” по дереву типов в направлении корня, понижающие – в обратном направлении, а перекрестные обеспечивают преобразование между типами, порожденными от одного корня, но принадлежащими разным ветвям дерева (например, между производными классами одного и того же базового класса, т.е. между “братьями” или… “сестрами”). Иногда может возникнуть необходимость и в преобразовании между базовыми классами одного производного класса (при множественном наследовании) – такое перекрестное преобразование также возможно (примеры перекрестных преобр. см. Т.Павловская, Гл.8, стр.236). Конечно, не всегда при этом речь идет о преобразовании классов как типов в “чистом”, так сказать, виде, чаще о преобразовании указателей и ссылок и в контексте работы с функциями. Само преобразование обеспечивается либо на этапе компиляции (статическое преобразование), либо динамически, т.е., во время выполнения программы. Итак, напомним, что для явного преобразования типов в C++ имеются следующие операции: const_cast, dynamic_cast, reinterpret_cast и static_cast.
Операция const_cast служит для удаления модификатора const. Обычно, она используется при передаче константного указателя в функцию на месте формального параметра, не имеющего модификатора const. Предполагается, что программист знает, что делает и уверен, что в теле ф-ии, объект, на который ссылается указатель, не изменяется. Синтаксис(тип выражения должен быть тот же, что и “тип”):
const_cast <тип> (выражение)
Операция dynamic_cast заслуживает большего внимания. Она применяется для преобразования указателей родственных классов иерархии, чаще – указателя базового типа в указатель на производный с проверкой допустимости преобразования. Синтаксис:
Дата добавления: 2015-11-16; просмотров: 59 | Нарушение авторских прав
<== предыдущая страница | | | следующая страница ==> |
Применение наследования | | | Dynamic_cast <тип *> (выражение). |