А.В. Столяров - Введение в язык Си++ (1114949), страница 19
Текст из файла (страница 19)
Относительно студента нас может интересовать код специальности, номер курса и показатель успеваемости(средний балл). Вместе с тем, несомненно, студент — это тоже человек,так что ему присущи и свойства, общие для всех персон: имя, пол игод рождения. Таким образом, мы можем перейти от общего (персона) кчастному (студент), добавив уточняющие сведения, и сделать это можнос помощью механизма наследования. На С и + + это записывается так:stru c t student : person {in t code;86in t y ea r; / / курсf lo a t average;Если теперь описать переменную типа student, она будет иметь все свойства структуры person плюс дополнительные поля:student s 1;strcp y (sl.n am e, "John Doe");s i .
sex = ’ m’ ;si,y e a r_ o f_ b irth = 1989;si.c o d e = 51311;s i.y e a r = 2;si.a v e ra g e = 4 .7 5 ;Структура person в данном случае называется базовой или р о д и т е л ь ской, а структура student — унаследованной или порож дённой,иногда дочерней. Также употребляются термины «предок» и «потомок»(в данном случае, соответственно, для person и student).name1sexйотзйyear_of_birth1codeyearaverageРис. 4.1.
Унаследованная структура данныхРасположение полей переменной s i в памяти показано на рис. 4.1.Здесь следует заметить, что поля, относящиеся к части структурыstudent, унаследованной от person, располагаются на тех же местах (потем же смещениям относительно начала), где они располагались бы вструктуре person. Получается, что мы можем при необходимости работать с переменной s i точно так, как если бы она была переменной типа87person, а не типа student (важно понимать, что обратное неверно: вструктуре person отсутствуют поля, которые ожидались бы от структуры student).В связи с этим язык С и + + р азр е ш ае т неявное преобразованиеадресов структур типа student в адреса структур типа person, или, вобщем случае, адресов структур-потом ков к адресам структурпредков.
Соответствующие преобразования разрешены как для указателей, так и для ссылок. Таким образом, например, возможны такиефрагменты кода:student s i ;person *р ;р = & sl;person &ref = s i ;Если в программе описана функция, принимающая на вход указательили ссылку на объект типа person, ей без каких-либо сложностей можнопередать, соответственно, указатель или ссылку на объект типа student:void f(p erson &pers) {// ...student s i ;}u ...f ( s i ) ; / / корректно!Это свойство предков, потомков и их адресов называется полиморф и зм .Если говорить точнее, полиморфизм не сводится только к преобразованиюуказателей и ссылок. Вообще говоря, полиморфизм в зависимости от контекстапонимается либо как способность некоторых объектов выступать в качестве объектов разных типов (в данном случае si выступает и как student, и как person),либо как способность некоторых функций или других операций работать с объектами разных типов (в данном случае f О работает как с person, так и с любымиеё потомками, в том числе student).§4.3.
Н аследован и е и методы ; кон структорыи д естр у к то р ыЧаще всего наследование применяется для структур и классов, имеющих функции-члены (методы). В дальнейшем изложении мы будем опускать слово «структура» и говорить только о классах, хотя всё, что будетсказано, может быть применено также и к структурам.Методы, имеющиеся в базовом классе, доступны и для порождённогокласса. Здесь необходимо отметить, что, если не предпринять специальных мер, то методы базового класса не будут ничего знать о том, что88их вызывают для объекта порождённого класса, то есть будут работатьтак же, как и для объектов базового класса; здесь мы имеем описанный в предыдущем параграфе эффект полиморфизма в применении кпараметру th is (см. §2.1.2).
Например, если мы сделаем класс «автомобиль» и предусмотрим для него операцию «заправка бензином», а потомпородим от него класс «грузовик» с дополнительными свойствами, тооперация «заправка бензином» будет доступна и для грузовика, причёмреализация этой операции не будет ничего знать про особенности грузовиков и будет работать с объектом типа «грузовик» точно так же, как ис объектом типа «автомобиль».Говорят, что о б ъ е к т порож дённого класса я в л я е т с я в полном смыслеслова т а к ж е и о б ъ е к т о м базового класса, в том числе для него доступныи все операции, которые доступны для базового класса. С другой стороны, как видно из рис.
4.1, объект порождённого класса содерж ит в себеобъект базового класса в качестве своей части. Никакого противоречиятут нет, это просто две разные модели восприятия или, если угодно, точки зрения: одна — точка зрения реализации (реализаторская семантика),вторая — точка зрения логики проектирования (пользовательская семантика) .Особого упоминания в связи с введением наследования заслуживают конструкторы и деструкторы. С какой бы точки зрения мы ни рассматривали порождённый объект, очевидно, что в момент его созданиятакже создаётся и объект базового класса, причём неважно, считаем мыего «частью» порождённого объекта или же его «другой ипостасью».
Таким же точно образом при уничтожении порождённого объекта исчезаети базовый объект. Следовательно, при создании объекта порождённогокласса должен отработать и конструктор базового класса, а при уничтожении такого объекта — деструктор базового класса. При этом, очевидно, в телах конструктора и деструктора порождённого класса должныбыть доступны все части объекта, для которого они вызываются.
Базовый объект, таким образом, должен быть инициализирован раньше, ауничтожен — позже объекта порождённого. Получается, что время существования объекта класса-наследника в его «базовой» ипостаси как бычуть-чуть больше, чем время существования его же в качестве объектасвоего собственного типа.С деструктором дела обстоят достаточно просто: компилятор сначала вызывает тело деструктора порождённого класса, а затем — тело деструктора базового класса.
С конструктором всё тоже могло бы бытьпросто (сначала вызвать тело конструктора базового класса, потом тело конструктора класса порождённого), если бы не то обстоятельство,что конструкторы (в общем случае) могут требовать входных параметров. С похожей проблемой мы уже сталкивались при рассмотрении классов, имеющих поля типа класс (см.
§ 2.16). Решается проблема в данном89случае совершенно аналогично: в описании конструктора порождённогокласса между заголовком и телом вставляется список инициализаторов,начинающийся с инициализатора базового класса (обозначаемого в данном случае именем класса) с указанием всех нужных параметров конструктора. Так, если А — базовый класс, конструктору которого требуются два параметра типа in t, В — класс, унаследованный от А, снабженный конструктором по умолчанию и имеющий поле in t i, то описаниеего конструктора может выглядеть так: .В ::В () : А(2, 3 ), 1(4) { / * . . .
* / }где 2 и З - параметры для конструктора базового класса, 4 — начальноезначение поля i.§4.4. Н аследован и е и защ и таВзаимодействие механизма наследования с механизмом защиты деталей реализации заслуживает отдельного разговора. Прежде всего отметим, что сам факт наследования одного класса от другого может в некоторых случаях рассматриваться как деталь реализации этого класса и,соответствено, нуждаться в сокрытии от остального кода программы.В связи с этим в С и + + различают наследование открытое (public)и закрытое (private).
Тип наследования указывается в заголовке классанепосредственно перед названием порождаемого класса, например:c la s s В : pu blic А { / * . . . * / } ;илиc la s s С : p riv a te А { / * . . . * / } ;Если в первом случае все свойства класса А (его открытые методы иполя, если такие в нём есть) будут доступны для объектов класса В отовсюду, то во втором случае — только из методов класса С, а вся остальнаяпрограмма вообще не будет знать, что класс С унаследован от А.Тип наследования можно не указывать, как мы это делали в примерена стр. 86. В этом случае будут действовать умолчания: для структурынаследование по умолчанию открытое (public), для класса — закрытое(private). В реальной жизни потребность в закрытом наследовании возникает редко, поэтому при описании наследуемых классов обычно указывают слово pu blic, а при описании наследуемых структур не указываютничего, полагаясь на умолчания.Заметим теперь, что для базового класса порождённый класс ничутьне «лучше» всей остальной программы и, как и любой недружественныйфрагмент кода, не должен иметь доступа к деталям реализации класса.90Поэтому, очевидно, закрытые поля и методы базового класса не будутдоступны порождённым классам.