|
Программирование >> Аргументация конструирования
Если вы этого не знали, это вовсе не говорит о том, что вы ЯВЛЯЕТЕСЬ чайником. Это значит, что вы не читали главу 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 так, как это показано ниже.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки. |