Программирование >>  Аргументация конструирования 

1 ... 71 72 73 [ 74 ] 75 76 77 ... 108



Если вы этого не знали, это вовсе не говорит о том, что вы ЯВЛЯЕТЕСЬ чайником. Это значит, что вы не читали главу 21, Наследование классов .

Аргумент X, передаваемый fn(). ддя экономии места и времени объявден как ссыдка на объект класса Student. Если бы этот аргумент передавался по значению, С++ пришлось бы при каждом вызове fп () конструировать новый объект Student. В зависимости от вида класса Studer.t и количества вызовов fn () в итоге это может занять много времени, тогда как при вызова fn (Students) или fn(student*) передается только адрес. Если вы не поняли, о чем я говорю, перечитайте главу 15,

Создание указателей на объекты .

Было бы неплохо, если бы строка х. calcTuition {> вызывала Student: : calcTuition О, когда х является объектом класса student, и когда х является объектом класса

GraduateStudent. Если бы C++ был настолько сообразителен , это было бы

действительно здорово! Почему? Об этом вы узнаете далее в главе.

Обычно компилятор уже на этапе компиляции решает, к какой именно функции обращается вызов. После того как вы щелкаете на кнопке, которая дает указание компилятору C++ (причем неважно - GNU C + + или, например, Visual C + + ) пересобрать программу, компилятор должен просмотреть ее и на основе используемых аргументов выбрать, какую именно перегружаемую функцию вы имели в виду.

В данном случае объявленный тип аргумента функции fn() не полностью описывает требования к функции. Хотя aprvMeHT и обьявлен как Student, он может оказаться также и GraduateStudent. Окончательное решение можно принять, только когда программа выполняется (это называется на этапе выполнения ). И только когда функция fn() уже вызвана, C++ может посмотреть на тип аргумента и решить, какая именно функция-член должна вызываться: из класса Student или из GraduateStudent.

Типы аргументов, с которыми вы сталкивались до этого времени, называются или типами этапа компиляции. Объявленным

типом аргумента х в любом случае является Student, поскольку так написано в объявлении функции fл (). Другой, текущий, тип называется типом этапа выполнения. В случае с примером функции fn () типом этапа выполнения аргумента х является если () вызывается

с s, и GraduateStudent, когда fn () вызывается с gs. Все понятно?

Способность решать на этапе выполнения, какую именно из нескольких перегружаемых функций в зависимости от текущего типа следует вызывать, называется полиморфишом иди поздним связыванием. Термин полиморфизм восходит к двум греческим корням - поли (т.е. многообразие) и морф (форма), доподненным широко известным греко-латинским суффиксом иш. С + + поддерживает полиморфизм. (Что и не удивительно. Тратил бы я столько времени на обсуждение полиморфизма, если бы он не

поддерживался C+ + !) Чтобы подчеркнуть противоположность позднему связыванию, выбор перегружаемой функции на этапе компиляции называют ранним связыванием.

Полиморфизм и позднее связывание - не совсем эквивалентные термины. Полиморфизм означает способность выбора из возможных вариантов

на этапе выполнения, тогда как позднее связывание - это всего лишь механизм, который используется языком C++ для осуществления полиморфизма. Разница здесь довольно тонкая.

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






Зачел ш/жен noMUiOfiusM

Полиморфизм является ключом (одним из связки), который способен открыть всю

мощь объектно-ориентированного программирования. Он настолько важен, что языки, не поддерживающие полиморфизм, не имеют права называться объектно-ориентированными.

Языки, которые поддерживают классы, но не поддерживают

физм, называются объектно-основанными. К таким языкам относится,

например, Ada.

Без полиморфизма от наследования было бы мало толку. Позвольте привести еще один пример, чтобы вы поняли, почему это так. Представим себе, что я написал действительно сложную программу, использующую некий класс, который называется - не будем далеко ходить за примером - student. После нескольких месяцев разработки, кодирования и тестирования я выставляю эту программу на всеобщее обозрение, чтобы услышать восторженные отзывы и критику от свих коллег. (Программа настолько крута , что уже заходит речь о передаче мне контрольного пакета акций

Microsoft... но не будем опережать события.)

Проходит время, и мой босс просит добавить в программу возможность работы

с аспирантами, которые хотя и очень похожи, но все-таки не идентичны обычным студентам (правда, аспиранты думают, что они совсем не похожи на студентов!). Мой босс не знает и не интересуется тем, что где-то глубоко в программе функция someFunction () вызывает функцию-член calcTuition () (такая уж работа у босса -

ни о чем не думать и не волноваться...).

void someFunction(Students s)

...то, что эта функция должна делать...

s.calcTuition();

/1 . функция продолжается.. -

Если бы C + + не поддерживал позднее связывание, мне бы пришлось отредактировать функцию someFunction!) приблизительно так, как приведено ниже, и добавить

ее в класс GradusteStudent.

#define STUDENT 1

ttdefine GRADUATESTUDENT 2

void someFunction (Students, s)

...TO, что эта функция должна делать... добавим тип члена, который будет индициоовать текущий тип объекта switch (s.type) t

case STUDENT:

s . Student -. -. calcTuition () ;

break; case GRADUATESTUDENT:

s.GraduateStudent::calcTuition();

break;

. нкцияпродолжается. . .



Мне бы пришлось добавить в класс переменную type. После этого я б]л бы вынужден добавить присвоения type = STUDENT и type = GRADUATESTUDENT к конструктору GraduateStudent. Значение переменной type отражало бы текущий тип объекта s. Затем мне бы пришлось добавить проверяющие команды, показанные в приведенном выше фрагменте программы, везде, где вызываются переопределяемые функции.

Это не так уж и трудно, если не обращать внимания на тр кши. Во-первых,

в данном примере описана только одна функция. Представьте себе, что

calcTuition () вызывается из нескольких мест и что этой функции придется выбирать не между двумя, а между пятью или десятью классами. Маловероятно, что я найду все места в программе, которые надо отредактировать.

Во-вторых, я должен изменить (читай - сломать) код, который б]л отлажен и работал, а местами б1л довольно запутан. Редактирование может занять много времени и стать довольно скучной процедурой, что обычно ослабляет мое внимание. Любое изменение может оказаться ошибочным и конфликтовать с существующим кодом. Кто знает?..

И наконец, после того как я завершу редактирование, отладку и тестирование программы, я должен буду поддерживать две ее версии (если, конечно, не перестану поддерживать исходную). Это означает наличие двух потенциальных источников проблем в случае выявления ошибок и необходимость отдельной системы систематизации (как вам такая тавтология?), чтобы содержать все это в порядке.

А теперь представьте себе, что случится, когда мой босс захочет добавить еше один класс (босс он такой: на все способен...). Мне придется не только повторить весь процесс сначала, а поддерживать три версии программы!

При наличии полиморфизма все, что потребуется сделать, - это добавить новый

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

На некотором философском уровне есть еше более важные причины для полиморфизма. Помните, как я готовил закуски в микроволновой печи? Можно сказать, что я действовал по принципу позднего связывания. Рецепт был таким: разогрейте закуску в печи. В нем не было сказано: если печь микроволновая, сделай так, а если конвекционная - эдак. В рецепте (читай - коде) предполагалось, что я (читай - тот, кто осуществляет позднее связывание) сам решу, какой именно разогрев (функцию-

член) выбрать, в зависимости от типа используемой печи (отдельного экземпляра

класса Oven) или ее вариаций (подклассов), например таких, как микроволновая печь (Microvawe). Так думают люди, и так же создаются языки программирования: чтобы дать людям возможность, не изменяя образа мыслей, создавать более точные модели реального мира.

Aiajc ftaotflaeifi шшии>/иризм

C++ поддерживает и раннее и позднее связывание; однако вы, наверное, удивитесь, узнав, что в C++ по умолчанию используется раннее связывание. Если немного

подумать, причина становится понятной. Полиморфизм требует несколько больше ресурсов (времени и памяти) для каждого вызова функции. Отцы-основатели C++ беспокоились о том, что любое изменение, которое они представят в C++ как усовершенствование его предшественника С, может стать поводом для неприятия этого языка в качестве системного языка программирования. Поэтому они сделали более эффективное раннее связывание используемым по умолчанию.

Чтобы сделать функцию-член полиморфной, программист на C + + должен пометить ее ключевым словом virtual так, как это показано ниже.



1 ... 71 72 73 [ 74 ] 75 76 77 ... 108

© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки.
Яндекс.Метрика