Energy
education
Объектно-ориентированный язык программирования. Разработан в 1998—2001 годах группой инженеров под руководством Андерса Хейлсберга в компании Microsoft как язык разработки приложений для платформы Microsoft .NET Framework и впоследствии был стандартизирован как ECMA-334 и ISO/IEC 23270.
19. Полиморфизм
Слово полиморфизм означает иметь много форм. В объектно-ориентированном программировании полиморфизмом часто понимается как «один интерфейс, множество функций».
Полиморфизм может быть статическим или динамическим. В статическом полиморфизме выбор определяется во время компиляции. В динамическом полиморфизме выбор определяется во время выполнения.
Статический полиморфизм
Механизм связывания функции с объектом во время компиляции называется ранним связыванием. Он также называется статическое связывание. C# использует два механизма для реализации статического полиморфизма. Это:
- Перегрузка метода
- Перегрузка оператора
Перегрузка метода
Вы можете иметь несколько определений для одного имени метода в одной области видимости. Определенные методы должны отличаться друг от друга по видам и/или количеству аргументов в списке аргументов. Нельзя перегрузить объявление методов, которые отличаются только типом возвращаемого значения.
Ниже приведен пример, в котором те же методы print() используются для печати различных типов данных:
using System; namespace PolymorphismApplication < class Printdata < void print(int i) < Console.WriteLine("Вывод int: ", i ); > void print(double f) < Console.WriteLine("Вывод float: " , f); > void print(string s) < Console.WriteLine("Вывод string: ", s); > static void Main(string[] args) < Printdata p = new Printdata(); // вызов метода print для вывода integer p.print(5); // вызов метода print для вывода float p.print(500.263); // вызов метода print для вывода string p.print("Привет C#"); Console.ReadKey(); >> >
Если приведенный выше код скомпилировать и выполнить, это приведет к следующему результату:
Вывод int: 5 Вывод float: 500.263 Вывод string: Привет C#
Динамический полиморфизм
C# позволяет создавать абстрактные классы, которые используются для обеспечения частичной реализации интерфейса. Реализация завершается, когда производный класс наследует от него. Абстрактные классы содержат абстрактные методы, которые осуществляются в производном классе. Производные классы имеют более специализированные функциональные возможности.
Обратите внимание на следующие правила об абстрактных классах:
- Вы не можете создать экземпляр абстрактного класса
- Вы не можете объявить абстрактный метод за пределами абстрактного класса
- Если класс объявлен как sealed, он не может быть унаследован, абстрактные классы не могут быть объявлены как sealed.
Следующая программа демонстрирует абстрактный класс:
using System; namespace PolymorphismApplication < abstract class Shape < public abstract int area(); >class Rectangle: Shape < private int length; private int width; public Rectangle( int a=0, int b=0) < length = a; width = b; >public override int area () < Console.WriteLine("Площадь прямоугольника :"); return (width * length); >> class RectangleTester < static void Main(string[] args) < Rectangle r = new Rectangle(10, 7); double a = r.area(); Console.WriteLine("Площадь: ",a); Console.ReadKey(); > > >
Если приведенный выше код скомпилировать и выполнить, это приведет к следующему результату:
Площадь прямоугольника : Площадь: 70
Если у вас есть метод, определенный в классе, который вы хотите реализовать в унаследованном классе, вы используете виртуальный метод. Виртуальные методы могут быть реализованы по-разному в разных унаследованных классах и вызов этих методов будет решаться во время выполнения.
Динамический полиморфизм реализуется абстрактными классами и виртуальными методами.
Следующая программа демонстрирует это:
using System; namespace PolymorphismApplication < class Shape < protected int width, height; public Shape( int a=0, int b=0) < width = a; height = b; >public virtual int area() < Console.WriteLine("Площадь родительского класса :"); return 0; >> class Rectangle: Shape < public Rectangle( int a=0, int b=0): base(a, b) < >public override int area () < Console.WriteLine("Площадь класса Rectangle :"); return (width * height); >> class Triangle: Shape < public Triangle(int a = 0, int b = 0): base(a, b) < >public override int area() < Console.WriteLine("Площадь класса Triangle :"); return (width * height / 2); >> class Caller < public void CallArea(Shape sh) < int a; a = sh.area(); Console.WriteLine("Площадь: ", a); > > class Tester < static void Main(string[] args) < Caller c = new Caller(); Rectangle r = new Rectangle(10, 7); Triangle t = new Triangle(10, 5); c.CallArea(r); c.CallArea(t); Console.ReadKey(); >> >
Если приведенный выше код скомпилировать и выполнить, это приведет к следующему результату:
Площадь класса Rectangle: Площадь: 70 Площадь класса Triangle: Площадь: 25
Администратор сайта: Колосов Михаил
email:
Copyright © 2011-2023. All rights reserved.
64. Разумно сочетайте статический и динамический полиморфизм
Статический и динамический полиморфизм дополняют друг друга. Следует ясно представлять себе их преимущества и недостатки, чтобы использовать каждый из них там, где он дает наилучшие результаты, и сочетать их так, чтобы получить лучшее из обоих миров.
Динамический полиморфизм предстает перед нами в форме классов с виртуальными функциями и объектов, работа с которыми осуществляется косвенно — через указатели или ссылки. Статический полиморфизм включает шаблоны классов и функций.
Полиморфизм означает, что данное значение может иметь несколько типов, а данная функция может принимать аргументы типов, отличающихся от точных типов ее параметров. «Полиморфизм представляет собой способ получить немного свободы динамической проверки типов, не теряя преимуществ статической проверки» — [Webber03].
Сила полиморфизма состоит в том, что один и тот же фрагмент кода может работать с разными типами, даже с теми, которые не были известны в момент написания этого кода. Такая «применимость задним числом» является краеугольным камнем полиморфизма, поскольку существенно увеличивает пригодность и возможность повторного использования кода (см. рекомендацию 37). (В противоположность этому мономорфный код работает только со строго конкретными типами, теми, для работы с которыми он изначально создавался.)
Динамический полиморфизм позволяет значению иметь несколько типов посредством открытого наследования. Например, Derived* p можно рассматривать как указатель не только на Derived, но и на объект любого типа Base, который прямо или косвенно является базовым для Derived (свойство категоризации). Динамический полиморфизм известен также как включающий полиморфизм, поскольку множество, моделируемое Base, включает специализации, моделируемые Derived.
Благодаря своим характеристикам динамический полиморфизм в С++ наилучшим образом подходит для решения следующих задач.
• Единообразная работа, основанная на отношении надмножество/подмножество. Работа с различными классами, удовлетворяющими отношению надмножество/подмножество (базовый/производный), может выполняться единообразно. Функция, работающая с объектом Employee (Служащий), будет работать и с объектами Secretary (Секретарь).
• Статическая проверка типов. В С++ все типы проверяются статически.
• Динамическое связывание и раздельная компиляция. Код, который использует иерархию классов, может компилироваться отдельно от этой иерархии. Это становится возможным благодаря косвенности, обеспечиваемой указателями (как на объекты, так и на функции).
• Бинарная согласованность. Модули могут компоноваться как статически, так и динамически, до тех пор, пока схемы виртуальных таблиц подчиняются одним и тем же правилам.
Статический полиморфизм посредством шаблонов также позволяет значению иметь несколько типов. Внутри шаблона
t может иметь любой тип, который можно подставить в f для получения компилируемого кода. Это называется «неявным интерфейсом» в противоположность явному интерфейсу базового класса. Таким образом достигается та же цель полиморфизма — написание кода, который работает с разными типами — но совершенно иным путем.
Статический полиморфизм наилучшим образом подходит для решения следующих задач.
• Единообразная работа, основанная на синтаксическом и семантическом интерфейсе. Работа с типами, которые подчиняются синтаксическому и семантическому интерфейсу, может выполняться единообразно. Интерфейсы в данном случае представляют синтаксическую сущность и не основаны на сигнатурах, так что допустима подстановка любого типа, который удовлетворяет данному синтаксису. Например, пусть дана инструкция int i = p->f(5);. Если p — указатель на класс Base, эта инструкция вызывает определенную функцию интерфейса, вероятно, virtual int f(int). Но если p имеет обобщенный тип, то этот вызов может быть связан со множеством различных вещей, включая, например, вызов перегруженного оператора operator->, который возвращает тип, в котором определена функция X f(double), где X — тип, который может быть преобразован в int.
• Статическая проверка типов. Все типы проверяются статически.
• Статическое связывание (мешает раздельной компиляции). Все типы связываются статически.
• Эффективность. Вычисления во время компиляции и статическое связывание позволяют достичь оптимизации и эффективности, недоступных при динамическом связывании.
Определите ваши приоритеты и используйте каждый вид полиморфизма там, где проявляются его сильные стороны.
Следует сочетать статический и динамический полиморфизм для того, чтобы получить преимущества обоих видов полиморфизма, а не для того, чтобы комбинировать их недостатки.
• Статика помогает динамике. Используйте статический полиморфизм для реализации динамически полиморфных интерфейсов. Например, у вас может быть абстрактный базовый класс Command, и вы определяете различные реализации в виде шаблона
template* . */> class ConcreteCommand: public Command
В качестве примеров можно привести реализации шаблонов проектирования Command и Visitor (см. [Alexandrescu01] и [Sutter04]).
• Динамика помогает статике. Обобщенный, удобный, статически связываемый интерфейс может использовать внутреннюю динамическую диспетчеризацию, что позволяет обеспечить одинаковую схему размещения объектов. Хорошими примерами могут служить реализации размеченных объединений (см. [Alexandrescu02b] и [Boost]) и параметр Deleter у tr1::shared_ptr (см. [C++TR104]).
• Прочие сочетания. Плохим является сочетание, при котором комбинируются слабые стороны обоих видов полиморфизма и результат получается хуже, чем при их отдельном использовании. Правильное сочетание должно комбинировать лучшее от обоих видов полиморфизма. Например, не помещайте виртуальные функции в шаблон класса, если только вы не хотите, чтобы каждый раз инстанцировались все виртуальные функции (в противоположность невиртуальным функциям шаблонных типов). В результате вы можете получить астрономический размер кода и чрезмерно ограничить ваш обобщенный тип, инстанцируя функциональность, которая никогда не используется.
[Alexandrescu01] §10 • [Alexandrescu02b] • [C++TR104] • [Gamma95] • [Musser01] §1.2-3, §17 • [Stroustrup00] §24.4.1 • [Sutter00] §3 • [Sutter02] §1 • [Sutter04] §17, §35 • [Vandevoorde03] §14 • [Webber03] §8.6
Читайте также
Статический вес
Статический вес Определение страниц, получающих недостаточный статический вес. Часть важных страниц может недополучать статический вес. Вместо этого наибольший вес может переходить к непродвигаемым и техническим страницам. Чтобы решить эту проблему, необходимо:?
Объекты имеют статический тип
Объекты имеют статический тип Один из выводов, который можно сделать из трех требований QueryInterfасе , состоит в том, что множество интерфейсов, поддерживаемых объектом, не может изменяться во времени. Спецификация СОМ четко требует, чтобы этот вывод был верен для всех
10.1. PSPICE как статический логический анализатор
10.1. PSPICE как статический логический анализатор Шаг 1 Начертите в редакторе SCHEMATICS схему, изображенную на рис. 10.1. Необходимые компоненты вы найдете в библиотеке EVAL.slb. Редактор для установления метки (out) можно открыть, дважды щелкнув мышью по соответствующему участку
43. Разумно пользуйтесь идиомой Pimpl
43. Разумно пользуйтесь идиомой Pimpl РезюмеС++ делает закрытые члены недоступными, но не невидимыми. Там, где это оправдывается получаемыми преимуществами, следует подумать об истинной невидимости, достигаемой применением идиомы Pimpl (указателя на реализацию) для реализации
1.1.3. Полиморфизм
1.1.3. Полиморфизм Термин «полиморфизм», наверное, вызывает самые жаркие семантические споры. Каждый знает, что это такое, но все понимают его по-разному. (Не так давно вопрос «Что такое полиморфизм?» стал популярным во время собеседования при поступлении на работу. Если его
Полиморфизм
Полиморфизм Третьим принципом ООП является полиморфизм. Он характеризует способность языка одинаково интерпретировать родственные объекты. Эта особенность объектно-ориентированного языка позволяет базовому классу определить множество членов (формально называемых
Совет 12. Разумно оценивайте потоковую безопасность контейнеров STL
Совет 12. Разумно оценивайте потоковую безопасность контейнеров STL Мир стандартного С++ выглядит старомодным и не подверженным веяниям времени. В этом мире все исполняемые файлы компонуются статически, в нем нет ни файлов, отображаемых на память, ни общей памяти. В нем нет
17.5.3. Статический вызов виртуальной функции
17.5.3. Статический вызов виртуальной функции Вызывая виртуальную функцию с помощью оператора разрешения области видимости класса, мы отменяем механизм виртуализации и разрешаем вызов статически, на этапе компиляции. Предположим, что мы определили виртуальную функцию isA()
Полиморфизм
Полиморфизм Полиморфизм представляет собой способность вируса в процессе работы менять свой код таким образом, чтобы максимально затруднить процесс своего обнаружения путем сигнатурного сканирования и частично эвристики.Особо следует отметить тот факт, что и сама
Полиморфизм
Полиморфизм При наследовании, требование статической типизации, о котором говорилось выше, становится ограничивающим, если бы оно означало, что каждая сущность типа C может быть связана только с объектом точно такого же типа С. Например в системе управления навигацией
Полиморфизм
Полиморфизм Иерархии наследования позволяют достаточно гибко работать с объектами, сохраняя надежность статической типизации. Поддерживающие их методы: полиморфизм и динамическое связывание — одни из самых фундаментальных аспектов архитектуры ПО, обсуждаемой в этой
Статический тип, динамический тип
Статический тип, динамический тип Название последнего свойства предполагает различение «статического типа» и «динамического типа». Тип, который используется при объявлении некоторого элемента, является статическим типом соответствующей ссылки. Если во время выполнения
Статический механизм
Статический механизм Устранить последнее неясности в понимании закрепленного объявления поможет следующее замечание: это чисто статический механизм, не предполагающий никаких изменений объектов в период выполнения. Все ограничения могут быть проверены в период
C # — Полиморфизм
Слово полиморфизм означает наличие многих форм. В парадигме объектно-ориентированного программирования полиморфизм часто выражается как «один интерфейс, несколько функций».
Полиморфизм может быть статическим или динамическим. В статическом полиморфизме ответ на функцию определяется во время компиляции. В динамическом полиморфизме он решается во время выполнения.
Статический полиморфизм
Механизм связывания функции с объектом во время компиляции называется ранним связыванием. Он также называется статической привязкой. C # предоставляет два метода для реализации статического полиморфизма. Они —
- Перегрузка функций
- Перегрузка оператора
Мы обсудим перегрузку оператора в следующей главе.
Перегрузка функции
Вы можете иметь несколько определений для одного и того же имени функции в той же области. Определение функции должно отличаться друг от друга по типам и / или количеству аргументов в списке аргументов. Вы не можете перегружать объявления функций, которые отличаются только возвращаемым типом.
В следующем примере показано использование функции print () для печати различных типов данных:
using System; namespace PolymorphismApplication < class Printdata < void print(int i) < Console.WriteLine("Printing int: ", i ); > void print(double f) < Console.WriteLine("Printing float: " , f); > void print(string s) < Console.WriteLine("Printing string: ", s); > static void Main(string[] args) < Printdata p = new Printdata(); // Call print to print integer p.print(5); // Call print to print float p.print(500.263); // Call print to print string p.print("Hello C++"); Console.ReadKey(); >> >
Когда приведенный выше код компилируется и выполняется, он производит следующий результат:
Printing int: 5 Printing float: 500.263 Printing string: Hello C++
Динамический полиморфизм
C # позволяет создавать абстрактные классы, которые используются для обеспечения частичной реализации класса интерфейса. Реализация завершается, когда производный класс наследуется от него. Абстрактные классы содержат абстрактные методы, которые реализуются производным классом. Производные классы имеют более специализированную функциональность.
Вот правила об абстрактных классах:
- Вы не можете создать экземпляр абстрактного класса
- Вы не можете объявить абстрактный метод вне абстрактного класса
- Когда класс объявляется запечатанным , его нельзя унаследовать, абстрактные классы не могут быть объявлены герметичными.
Следующая программа демонстрирует абстрактный класс:
using System; namespace PolymorphismApplication < abstract class Shape < public abstract int area(); >class Rectangle: Shape < private int length; private int width; public Rectangle( int a = 0, int b = 0) < length = a; width = b; >public override int area () < Console.WriteLine("Rectangle class area :"); return (width * length); >> class RectangleTester < static void Main(string[] args) < Rectangle r = new Rectangle(10, 7); double a = r.area(); Console.WriteLine("Area: ",a); Console.ReadKey(); > > >
Когда приведенный выше код компилируется и выполняется, он производит следующий результат:
Rectangle class area : Area: 70
Когда у вас есть функция, определенная в классе, который вы хотите реализовать в унаследованном классе (-ах), вы используете виртуальные функции. Виртуальные функции могут быть реализованы по-разному в разных унаследованных классах, и вызов этих функций будет решаться во время выполнения. Динамический полиморфизм реализуется абстрактными классами и виртуальными функциями.
Следующая программа демонстрирует это:
using System; namespace PolymorphismApplication < class Shape < protected int width, height; public Shape( int a = 0, int b = 0) < width = a; height = b; >public virtual int area() < Console.WriteLine("Parent class area :"); return 0; >> class Rectangle: Shape < public Rectangle( int a = 0, int b = 0): base(a, b) < >public override int area () < Console.WriteLine("Rectangle class area :"); return (width * height); >> class Triangle: Shape < public Triangle(int a = 0, int b = 0): base(a, b) < >public override int area() < Console.WriteLine("Triangle class area :"); return (width * height / 2); >> class Caller < public void CallArea(Shape sh) < int a; a = sh.area(); Console.WriteLine("Area: ", a); > > class Tester < static void Main(string[] args) < Caller c = new Caller(); Rectangle r = new Rectangle(10, 7); Triangle t = new Triangle(10, 5); c.CallArea(r); c.CallArea(t); Console.ReadKey(); >> >
Когда приведенный выше код компилируется и выполняется, он производит следующий результат:
Rectangle class area: Area: 70 Triangle class area: Area: 25
Полиморфизм. Виртуальные функции. Общие понятия. Спецификаторы virtual и override . Примеры
Виртуальные функции реализуют так называемый полиморфизм. Термин «полиморфизм» происходит от греческих слов poly (много) и morphos (форма). Полиморфизм – это свойство программного кода изменять свое поведение в зависимости от ситуации, возникающей при выполнении программы. В контексте реализации полиморфизм – это технология вызова виртуальных функций, реализуемых в иерархически связанных классах. Иерархия классов формируется на базе механизма наследования.
С понятием полиморфизма тесно связано понятие виртуальная функция. Это специальным образом оформленная функция, которая может быть в так называемом полиморфном состоянии – состоянии, при котором вызов нужной функции из набора виртуальных формируется на этапе позднего связывания. Понятие позднее связывание означает, что код вызова нужной функции формируется при выполнении программы. Иными словами, в исходном коде вызов функции только обозначается без точного указания того, какая именно функция должна быть вызвана. Объект, для которого вызывается виртуальная функция, имеет общее значение. Конкретный объект и соответствующая ему функция будут сформированы на этапе выполнения программы.
Механизм виртуальных функций реализует основополагающий принцип полиморфизма: «один интерфейс, несколько реализаций» или «один интерфейс, несколько методов».
Как известно, существует также и раннее связывание. При раннем связывании известно, какие объекты используются при вызове функции в каждом случае. Это накладывает ограничения на возможности программного кода. Изменение объектов для функций с одинаковыми именами невозможно в процессе выполнения программы, это изменение нужно программировать каждый раз вручную (это и есть ограничение кода).
Не все задачи нуждаются в использовании позднего связывания. Выбор того, какой вид связывания будет использоваться в программе, зависит от специфики решаемой задачи.
Для реализации позднего связывания требуется следующее:
- классы обязаны образовывать иерархию с помощью механизма наследования;
- в иерархии классов были определены функции, имеющие одинаковое имя и список параметров;
- функции с одинаковым именем и параметрами должны быть отмечены как виртуальные (с ключевым словом virtual ).
Относительно парадигмы классов и объектов для полиморфизма можно выделить следующие характерные особенности:
- полиморфизм не характеризует объект;
- реализацию полиморфизма определяет архитектура класса;
- полиморфизм является характеристикой функций-членов класса;
- весь класс не может быть полиморфным, полиморфными могут быть только функции члены класса.
2. Виды полиморфизма. Динамический полиморфизм. Виртуальная функция. Организация цепочки виртуальных функций. Спецификаторы virtual и override
В языке C++ есть возможность реализовывать два вида полиморфизма:
- статический. Этот вид полиморфизма достигается путём использования перегруженных функций (раннее связывание), шаблонов классов и перегрузки операторов. Более подробно о перегрузке функций можно прочитать здесь , о шаблонах классов здесь и о перегрузке операторов здесь ;
- динамический. В этом случае используется наследование в сочетании с виртуальными функциями (позднее связывание).
Виртуальная функция – это функция, объявляемая в базовом классе и переопределяемая в производном классе. Производный класс по своему усмотрению реализует виртуальную функцию. Чтобы объявить виртуальную функцию, используется ключевое слово virtual .
Сигнатура виртуальной функции, объявленная в базовом классе, определяет вид интерфейса, реализуемого этой функцией. Интерфейс определяет способ вызова виртуальной функции. Для каждого конкретного класса виртуальная функция имеет свою реализацию, обеспечивающую выполнение действий, свойственных только этому классу. Таким образом, виртуальная функция для конкретного класса является неким уникальным (конкретным) методом (specific method).
В наиболее упрощенном виде объявление виртуальной функции в классе может быть следующим:
class BaseClass < virtual return_type FuncNameVirtual(list_of_parameters) < // . > >;
- FuncNameVirtual() – имя виртуальной функции;
- return_type – тип, возвращаемый функцией;
- list_of_parameters – список параметров, которые получает функция.
В унаследованном классе виртуальная функция FuncNameVirtual() продолжает цепочку виртуальных функций для классов низших уровней. Для этого не обязательно указывать ключевое слово virtual , поскольку это слово уже указано в базовом классе BaseClass .
class DerivedClass < return_type FuncNameVirtual(list_of_parameters) < // Это также виртуальная функция, которая переопределяет функцию базового класса. // . > >
Возможны ситуации, когда в производном классе объявлена функция, которая может восприниматься как виртуальная, однако она не есть виртуальной. Примеры таких ситуаций:
- функция с такой же сигнатурой как в базовом классе, но объявленная как константная ( const );
- функция, которой передаются аргументы типа, совместимого с аргументами функции базового класса.
В языке C++ в унаследованном классе, виртуальная функция, которая переопределяет одноименную функцию базового класса, может быть объявлена с спецификатором override . Хотя этот спецификатор не обязателен, объявление нужно для лучшей информативности. При беглом осмотре унаследованного класса сразу видно виртуальные функции ( override ). Исходя из этого, приблизительный вид унаследованного класса может быть примерно следующим
class DerivedClass < return_type FuncNameVirtual(list_of_parameters) override < // Для цепочки виртуальных функций так нужно делать всегда // . > >
После указания спецификатора override программист имеет лучшую информативность о том, что эта функция виртуальна и она переопределяет одноименную виртуальную функцию базового класса.
Если для функции, объявленной со спецификатором override в производном классе DerivedClass , нет подходящей виртуальной функции в базовом классе BaseClass , компилятор сгенерирует ошибку.
3. Случаи реализации полиморфизма
Вызов виртуальной функции из клиентского кода такой же, как и вызов невиртуальной функции. Основным здесь является правильная организация вызова виртуальной функции.
Если в иерархии классов реализованы виртуальные функции, то полиморфизм реализуется в следующих случаях:
- при объявлении указателя ( * ) на базовый класс и вызове виртуальной функции соответствующего экземпляра класса, являющегося частью иерархии. Как известно, в этом случае указатель на базовый класс может быть установлен в значение экземпляров производных классов. После этого вызывается соответствующая виртуальная функция;
- при передаче указателя ( * ) на базовый класс в некоторую функцию, вызывающую виртуальную функцию базового класса с помощью оператора -> (доступ по указателю);
- при передаче ссылки ( & ) на базовый класс в некую функцию, вызывающую виртуальную функцию базового класса с помощью оператора ‘ .‘ (точка, доступ по ссылке).
4. Примеры реализации полиморфизма
4.1. Пример полиморфизма для двух классов. Вызов виртуальной функции по указателю ( -> ). Анализ кода
В примере демонстрируется механизм полиморфизма, заключающийся в передаче некоторой функции указателя на базовый класс. Базовый класс и его подкласс (класс, унаследованный от базового) содержат виртуальную функцию PrintInfo() без параметров, которая выводит информационное сообщение о данном классе.
#include iostream> using namespace std; // Базовый класс class Base < public: // virtual - признак виртуальной функции virtual void PrintInfo() < cout "Base." >; // Класс, унаследованный от класса Base, // важно: здесь должен быть модификатор public class Derived : public Base < public: virtual void PrintInfo() override // спецификатор override желательно указывать < cout "Derived." >; void main() < // 1. Создать экземпляры базового и производного класса Base obj1; Derived obj2; // 2. Объявить указатель на базовый класс Base* p; // 3. Использовать правило: указатель на базовый класс может указывать // на любой экземпляр базового и производного от него класса. // Ниже демонстрируется полиморфизм. // 3.1. Установить указатель p на экземпляр базового класса obj1 // и вызвать PrintInfo() p = &obj1; p->PrintInfo(); // Base // 3.2. Установить указатель p на экземпляр производного класса // и вызвать PrintInfo() p = &obj2; p->PrintInfo(); // Derived - это есть полиморфизм (слово virtual) >
Результат выполнения программы
Base. Derived.
Проанализируем вышеприведенный код.
В примере объявляются два класса Base и Derived , образующие иерархию с помощью механизма наследования.
Для обеспечения полиморфизма используется правило: для классов, образующих иерархию, указатель на базовый класс может ссылаться на экземпляр базового класса и любого унаследованного класса из этой иерархии. Поэтому в программе объявляется строка
. // 3. Объявить указатель на базовый класс Base* p; .
Теперь указателю p можно присваивать адрес любого экземпляра классов Base и Derived . Сначала присваивается адрес экземпляра obj1 типа Base и вызывается метод PrintInfo()
p = &obj1; p->PrintInfo(); // Base
Вывод будет прогнозированным – слово «Base».
Затем указателю p присваивается адрес экземпляра obj2 типа Base и вызывается метод PrintInfo()
p = &obj2; p->PrintInfo();
Вывод будет «Derived». То есть будет вызван метод PrintInfo() производного класса, что нам и нужно. Вызов этого метода обеспечивает ключевое слово virtual в объявлении функции PrintInfo() базового класса Base .
Если в классе Base перед объявление функции PrintInfo() убрать ключевое слово virtual , то в следующем коде
p = &obj2; p->PrintInfo();
будет вызван метод PrintInfo() класса Base , а не класса Derived . Это означает, что полиморфизм не будет поддерживаться, а всегда будет вызываться функция базового класса. В результате программа выведет
Base. Base.
Таким образом, функция PrintInfo() класса Derived для указателя p на базовый класс Base будет недоступна.
Вывод. Полиморфизм реализует правило «один интерфейс, много реализаций». В нашем случае интерфейс один – это объявление и вызов функции PrintInfo()
p->PrintInfo();
Но в зависимости от того, на какой объект указывает указатель p , будет вызван соответствующий метод PrintInfo() – это и есть много реализаций.
4.2. Пример, объясняющий полиморфизм для трех классов. Передача в функцию указателя ( * ) на базовый класс
Приводится пример 3-х классов CalcLength , CalcArea , CalcVolume , в которых в виртуальной функции Calc() возвращается соответственно длина окружности, площадь круга и объем шара.
Для демонстрации создается некоторая функция ShowResult() , в которую передается указатель на базовый класс. Функция вызывает виртуальную функцию Calc() по указателю. В теле функции ShowResult() неизвестно, экземпляр какого класса будет ей передан. Экземпляр будет сформирован во время выполнения.
#include iostream> using namespace std; // Класс, содержащий функцию вычисления длины окружности class CalcLength < public: // Виртуальная функция virtual double Calc(double radius) < return 2 * 3.1415 * radius; > >; // Класс, содержащий функцию вычисления площади окружности class CalcArea : public CalcLength < public: // Виртуальная функция double Calc(double radius) override < return 3.1415 * radius * radius; > >; // Класс, содержащий функцию вычисления объема шара class CalcVolume : public CalcArea < public: // Виртуальная функция double Calc(double radius) override < return 4.0 / 3 * 3.1415 * radius * radius * radius; > >; // Некоторая функция, получающая указатель на базовый класс ClassLength и параметр радиуса, // в данной функции демонстрируется полиморфизм void ShowResult(CalcLength* p, double radius) < // Вызов метода Calc() по указателю p. // Для данной функции неизвестно, метод какого класса будет вызван. // Нужный метод будет сформирован во время выполнения - это есть полиморфизм double res = p->Calc(radius); // cout "Result color: #0000ff;">void main() < // 1. Объявить указатель на базовый класс - это важно CalcLength* p = nullptr; // 2. Создать экземпляры 3-х классов CalcLength obj1; CalcArea obj2; CalcVolume obj3; // 3. Ввести номер функции int num; cout "Enter number of function (1-3): "; cin >> num; if ((num < 1) || (num >3)) return; // 4. Ввести радиус double radius; cout "radius color: #008000;"> // 5. Установить указатель p в зависимости от введеного num if (num == 1) p = &obj1; if (num == 2) p = &obj2; if (num == 3) p = &obj3; // 6. Вызвать метод ShowResult() // Нужный объект подставляется в зависимости от ситуации ShowResult(p, radius); // 7. Вызвать метод ShowResult() непосредственно подставляя экземпляр класса if (num == 1) ShowResult(&obj1, radius); if (num == 2) ShowResult(&obj2, radius); if (num == 3) ShowResult(&obj3, radius); >
4.3. Пример полиморфизма для трех классов. Вызов виртуальной функции в методе. Передача в метод ссылки ( & ) на базовый класс
В примере демонстрируется полиморфизм на основе ссылки на базовый класс, передаваемый в функцию DemoPolymorphism() .
Объявляется 3 класса с именами A , B , C . Класс A является базовым для класса B . Класс B является базовым для класса C . Классы содержат только один метод, выводящий название класса. С помощью механизма полиморфизма в функцию DemoPolymorphism() передается ссылка на один из экземпляров. В соответствии с переданным экземпляром вызывается требуемый метод.
#include iostream> using namespace std; // Класс A - базовый класс в иерархии A class A < public: virtual void Show() < cout "A::Show()" >; // Класс B - унаследованный от класса A class B : public A < public: // виртуальный метод - переопределяет одноименный метод класса A void Show() override < cout "B::Show()" >; // Класс C - унаследован от класса B class C : public B < public: // виртуальный метод - переопределяет одноименный метод класса B void Show() override < cout "C::Show()" >; // Функция, получающая ссылку на базовый класс A, // в функции демонстрируется полиморфизм. void DemoPolymorphism(A& ref) < // Вызов виртуального метода, метод определяется при выполнении. // Здесь неизвестно, метод какого класса A, B или C нужно вызвать. ref.Show(); > void main() < // 1. Объявить экземпляры классов A, B, C A objA; B objB; C objC; // 2. Вызвать функцию DemoPolymorphism(), // в зависимости от того, какой экземпляр передается, // вызывается соответствующий метод класса. DemoPolymorphism(objA); // A::Show() DemoPolymorphism(objB); // B::Show() DemoPolymorphism(objC); // C::Show() >
После запуска на выполнение программа выдаст следующий результат
A::Show() B::Show() C::Show()
Если при объявлении метода Show() в классах A , B , C убрать спецификаторы virtual и override , то полиморфизм поддерживаться не будет. А это значит, что при вызове метода DemoPolymorphism() все передаваемые ссылки на экземпляры objA , objB , objC будут конвертироваться в ссылку на базовый класс A& . Как следствие, 3 раза будет вызван метод Show() класса A , и программа выдаст следующий результат
A::Show() A::Show() A::Show()
Связанные темы
- Абстрактный класс. Чисто виртуальная функция. Примеры
- Типы отношений между классами is-a, has-a. Примеры. Агрегация. Композиция