Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Методические указания.doc
Скачиваний:
9
Добавлен:
18.04.2015
Размер:
619.52 Кб
Скачать

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

Механизм наследования является ключевым в концепции объектно-ориентированного программирования и сводится к следующему тезису: произвольный класс (класс-потомок, класс-наследник) может быть объявлен как наследник другого класса (класса-предка). При этом, потомок наследует все переменные и методы предка. Таким образом, если мы объявим класс «водитель», как наследника класса «персона»,

class Driver: public Person {

char drivingLicence[10]; -- Номер водительского удостоверения

};

то в памяти каждый объект класса «водитель» будет представлять собой расширение класса «персона» по составу переменных:

То же самое происходит с методами этих классов: методы класса «персона» будут входить в состав методов всех его наследников, в том числе класса «водитель».

В принципе, класс-потомок может быть наследован от нескольких классов-предков одновременно. Хотя в языках Java и C# на этот счет существуют ограничения, которые мы обсудим позднее.

Динамические методы

Еще одним критерием классификации методов класса является свойство динамичности. При обсуждении проблемы динамических методов, затронем тесно связанный с этим вопрос динамического связывания.

Существует два вида связывания (вызова) методов класса: динамический и статический. При статическом связывании компилятор указывает (в объектном коде программы) непосредственно адрес вызываемого метода. Например, следующим образом:

Рисунок 2 Вызов методов при статическом связывании

где стрелки – вызов соответствующего метода по фиксированному адресу.

При динамическом связывании – адрес вызываемого метода вычисляется в зависимости от того, к какому классу принадлежит тот объект, для которого он вызывается. В этом случае на стадии компиляции в объектном коде фиксируется лишь индекс (номер) динамического метода в рамках так называемой таблицы виртуальных методов. Таблица виртуальных методов создается компилятором для каждого класса, содержащего динамические методы, и содержит указатели на все динамические методы этого класса (каждый по своему индексу). Таблица виртуальных методов загружается в момент инициализации программы из ее объектного кода.

Для того, чтобы определить ту таблицу виртуальных методов, которая должна использоваться при вызове методов конкретного объекта, объекты всех классов, содержащих динамические методы, включают дополнительную переменную, указывающую на таблицу виртуальных методов. Например, если класс «персона» имеет динамические методы, то все его объекты будут представлены в памяти с использованием дополнительной переменной vmtPtr (указатель на таблицу виртуальных методов), не объявленную в явном виде среди переменных класса:

Эта дополнительная переменная заполняется каждый раз при создании в памяти очередного объекта. Код по ее заполнению встраивается в программу на стадии компиляции автоматически, прозрачно для программиста. Эта функция компилятора основана на том, что зная, к какому классу относится конкретный создаваемый объект, компилятор может определить адрес соответствующей таблицы виртуальных методов, создаваемой им самим же. В результате, каков бы ни был тип указателя на объект, переменная vmtPtr будет содержать указатель на таблицу виртуальных методов именного того класса, объект которого был создан. Например, мы можем создать класс «водитель» и присвоить указатель на него типу «указатель на персону»:

Person * p = new Driver;

Несмотря на то, что на объект указывает переменная p типа «указатель на персону», скрытая переменная vmtPtr этого объекта будет содержать указатель на таблицу виртуальных методов класса «водитель».

При динамическом связывании компилятор автоматически встраивает в код программы следующий способ вызова динамического метода на основании его индекса:

  • по указателю на объект, в контексте которого вызывается метод, берется указатель на таблицу виртуальных методов (с помощью дополнительной переменной, описанной выше);

  • по индексу динамического метода берется указатель на вызываемый динамический метод;

  • вызывается метод по полученному указателю.

Выше мы изложили понятие динамического связывания, теперь дадим определение динамическому методу. Динамический метод – это метод класса, к которому применяется метод динамического связывания, если явно не указано обратное – необходимость использовать статическое связывание. Все динамические методы класса присутствуют в его таблице виртуальных методов. Конечный метод является противоположностью динамического метода в том смысле, что к нему динамическое связывание никогда не применяется. Конечные методы отсутствуют в таблице виртуальных методов. Разницу между динамическим и конечным методам можно проиллюстрировать на следующем примере на языке C++:

Person * p = new Driver;

p->aMethod();

Допустим, что в классе «персона» и классе «водитель» объявлено два метода aMethod с одинаковой сигнатурой. В том случае, если методы aMethod являются динамическими в обоих классах, будет вызван метод класса «водитель» в соответствии с правилом динамического связывания. Если же методы являются конечными, то будет вызван метод класса «персона» в соответствии с правилом статического связывания (так как переменная p указывает на объект класса «персона»).

Возможность использования индекса метода в таблице виртуальных методов в качестве ссылки на него основана на том, что если динамические методы в базовом и производном классах имеют одинаковую сигнатуру, то в таблицах виртуальных методов для обоих классов эти методы будут иметь одинаковый индекс. Например, для классов «персона» и «водитель» таблицы виртуальных методов будут содержать методы aMethod (если они динамические) с одинаковыми индексами:

В нашем примере одновременно используется динамическое и статическое связывание методов. M1 и M2 связываются статически. M1 и Person::aMethod, Driver::aMethod связываются динамически. На стадии компиляции в объектный код M1 встраивается вызов динамического метода по индексу 23. Так как все такие методы имеют одинаковую сигнатуру, то на процессе передачи параметров динамическое связывание никак не сказывается. На стадии выполнения программы, в зависимости от того, на какую таблицу виртуальных методов ссылается конкретный объект, вызывается либо динамически метод Person::aMethod, либо Driver::aMethod.

При этом, для динамического метода может быть указана необходимость использования статического связывания в явном виде. Например, на языке C++ для вышеприведенного примера это будет выглядеть так:

Person * p = new Driver;

p->Person::aMethod();

В этом случае, какими бы не были методы aMethod – динамическими или конечными, – будет вызван метод класса «персона». Например, даже если методы aMethod являются динамическими, будет использовано статическое связывание:

Существует частный случай динамического метода – абстрактный метод. Абстрактный метод отличается тем, что он не имеет реализации (тела), для него задается только объявление (заголовок). Класс, содержащий абстрактные методы, также является абстрактным. В программе нельзя создать объекты абстрактного класса – будет выдана ошибка на стадии компиляции, – но можно объявлять указатели на абстрактные классы. Заметим, что существование указателя на абстрактный класс никак не подразумевает создание объекта абстрактного класса, так как указатель на базовый класс может указывать на объект производного класса. Например, если класс «персона» является абстрактным, то

Person * p = new Driver;

допустимо, а

Person * p = new Person;

недопустимо.

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

Например, допустим класс «персона» является абстрактным и у него есть два производных класса: «водитель» и «спортсмен». Тогда мы можем объявить массив указателей на объекты класса «персона»,

Person * p[];

каждому указателю которого может быть присвоен как адрес объекта класса «водитель», так и адрес объекта класса «спортсмен»:

В результате, массив персон может обрабатываться унифицированным образом вне зависимости от истинного типа объекта, расположенного в памяти. Объекты классов «водитель» и «спортсмен» умышленно изображены на рисунке разного размера, чтобы подчеркнуть тот факт, что в памяти они могут занимать разное количество байт.