Полиморфизм в с ++

НАСКОЛЬКО МНЕ ИЗВЕСТНО:

C++ обеспечивает три различных типа полиморфизма.

  • Виртуальные функции
  • Перегрузка имени функции
  • Перегрузка оператора

В дополнение к вышеупомянутым трем типам полиморфизма существуют и другие виды полиморфизма:

  • время выполнения
  • время компиляции
  • специальный полиморфизм
  • параметрический полиморфизм

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

Но для двух других

специальный полиморфизм:

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

параметрический полиморфизм:

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

я с трудом их понимаю:(

Может ли кто-нибудь объяснить их, если это возможно, на примере? Я надеюсь, что ответы на эти вопросы будут полезны для многих новых отрывков из их колледжей.

8 ответов

Решение

Понимание / требования к полиморфизму

Чтобы понять полиморфизм - как этот термин используется в вычислительной науке - он помогает начать с простого теста и определения его. Рассматривать:

    Type1 x;
    Type2 y;

    f(x);
    f(y);

Вот, f() должен выполнить какую-то операцию и получает значения x а также y в качестве входных данных.

Чтобы показать полиморфизм, f() должен уметь работать со значениями как минимум двух разных типов (например, int а также double), находя и выполняя отдельный код, соответствующий типу.


C++ механизмы полиморфизма

Явный программист-определенный полиморфизм

Ты можешь написать f() так что он может работать с несколькими типами любым из следующих способов:

  • Препроцессирование:

    #define f(X) ((X) += 2)
    // (note: in real code, use a longer uppercase name for a macro!)
    
  • Перегрузки:

    void f(int& x)    { x += 2; }
    
    void f(double& x) { x += 2; }
    
  • Шаблоны:

    template <typename T>
    void f(T& x) { x += 2; }
    
  • Виртуальная рассылка:

    struct Base { virtual Base& operator+=(int) = 0; };
    
    struct X : Base
    {
        X(int n) : n_(n) { }
        X& operator+=(int n) { n_ += n; return *this; }
        int n_;
    };
    
    struct Y : Base
    {
        Y(double n) : n_(n) { }
        Y& operator+=(int n) { n_ += n; return *this; }
        double n_;
    };
    
    void f(Base& x) { x += 2; } // run-time polymorphic dispatch
    

Другие связанные механизмы

Предоставляемый компилятором полиморфизм для встроенных типов, стандартных преобразований и приведения / приведения обсуждается позже для полноты:

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

терминология

Дальнейшая категоризация

Учитывая вышеописанные полиморфные механизмы, мы можем классифицировать их различными способами:

  • Когда выбирается полиморфный код для конкретного типа?

    • Время выполнения означает, что компилятор должен генерировать код для всех типов, которые программа может обрабатывать во время работы, и во время выполнения выбирается правильный код (виртуальная отправка)
    • Время компиляции означает, что выбор специфичного для типа кода выполняется во время компиляции. Следствие этого: скажем, программа называется только f выше с int аргументы - в зависимости от используемого полиморфного механизма и встроенных вариантов компилятор может избежать генерации любого кода для f(double) или сгенерированный код может быть отброшен в какой-то момент компиляции или компоновки. (все механизмы выше, кроме виртуальной рассылки)

  • Какие типы поддерживаются?

    • Ad-hoc означает, что вы предоставляете явный код для поддержки каждого типа (например, перегрузка, специализация шаблона); Вы явно добавляете поддержку типа "для этого" (в соответствии со значением ad hoc), некоторые другие "это" и, возможно, "это" тоже;-).
    • Параметрический смысл: вы можете просто попытаться использовать функцию для различных типов параметров, не предпринимая особых действий, чтобы включить их поддержку (например, шаблоны, макросы). Объект с функциями / операторами, которые действуют как шаблон / макрос, ожидает, что 1 - это все, что шаблону / макросу нужно для своей работы, причем точный тип не имеет значения."Концепции", вырезанные из C++11, помогают выразить и реализовать такие ожидания - будем надеяться, что они превратят его в более поздний Стандарт.

      • Параметрический полиморфизм обеспечивает типизацию утки - понятие, приписываемое Джеймсу Уиткомбу Райли, который, по-видимому, сказал: "Когда я вижу птицу, которая ходит, как утка, и плавает, как утка, и крякает, как утка, я называю эту птицу уткой".,

        template <typename Duck>
        void do_ducky_stuff(const Duck& x) { x.walk().swim().quack(); }
        
        do_ducky_stuff(Vilified_Cygnet());
        
    • Полиморфизм подтипа (он же включение) позволяет работать с новыми типами без обновления алгоритма / функции, но они должны быть получены из одного базового класса (виртуальная диспетчеризация)

1 - Шаблоны очень гибкие. СФИНАЕ (см. Также std::enable_if) эффективно допускает несколько наборов ожиданий для параметрического полиморфизма. Например, вы можете закодировать это, когда тип обрабатываемых данных имеет .size() член, вы будете использовать одну функцию, в противном случае другую функцию, которая не нуждается .size() (но, по-видимому, страдает в некотором роде - например, использование медленнее strlen() или не печатать как полезное сообщение в журнале). Вы также можете указать произвольное поведение, когда шаблон создается с конкретными параметрами, либо оставляя некоторые параметры параметрическими ( частичная специализация шаблона), либо нет ( полная специализация).

"Полиморфный"

Альф Штейнбах отмечает, что в C++ Standard полиморфный относится только к полиморфизму времени выполнения с использованием виртуальной диспетчеризации. Общий Комп. Sci. значение более содержательно, согласно глоссарию создателя C++ Бьярна Страуструпа ( http://www.stroustrup.com/glossary.html):

полиморфизм - предоставление единого интерфейса сущностям разных типов. Виртуальные функции обеспечивают динамический (во время выполнения) полиморфизм через интерфейс, предоставляемый базовым классом. Перегруженные функции и шаблоны обеспечивают статический (во время компиляции) полиморфизм. TC++PL 12.2.6, 13.6.1, D&E 2.9.

Этот ответ - как и вопрос - связывает возможности C++ с Comp. Sci. терминология.

обсуждение

Стандарт C++ использует более узкое определение "полиморфизм", чем Comp. Sci. сообщество, чтобы обеспечить взаимопонимание для вашей аудитории считают...

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

Тем не менее, для того, чтобы стать великим программистом C++, важно понять, что же на самом деле делает полиморфизм для вас...

позволяя вам написать "алгоритмический" код один раз, а затем применить его ко многим типам данных

... а затем очень хорошо осознайте, насколько различные полиморфные механизмы соответствуют вашим реальным потребностям.

Костюмы полиморфизма времени выполнения:

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

Когда нет четкого драйвера для полиморфизма во время выполнения, параметры времени компиляции часто предпочтительнее. Рассматривать:

  • так называемый аспект шаблонных классов предпочтительнее, чем толстые интерфейсы, терпящие неудачу во время выполнения
  • SFINAE
  • CRTP
  • оптимизация (многие из них включают в себя вставку и устранение мертвого кода, развертывание цикла, статические массивы на основе стека и куча)
  • __FILE__, __LINE__ конкатенация строковых литералов и другие уникальные возможности макросов (которые остаются злыми;-))
  • поддерживается семантическое использование тестов шаблонов и макросов, но не ограничивает искусственно, как эта поддержка предоставляется (поскольку виртуальная диспетчеризация требует точно совпадающих переопределений функций-членов)

Другие механизмы, поддерживающие полиморфизм

Как и было обещано, для полноты изложения рассматриваются несколько второстепенных тем:

  • предоставляемые компилятором перегрузки
  • преобразования
  • проливает / принуждение

Этот ответ завершается обсуждением того, как вышеперечисленное сочетается для расширения возможностей и упрощения полиморфного кода, особенно параметрического полиморфизма (шаблоны и макросы).

Механизмы для отображения на типовые операции

> Неявные перегрузки, предоставляемые компилятором

Концептуально компилятор перегружает многие операторы для встроенных типов. Концептуально он не отличается от указанной пользователем перегрузки, но указан в списке, так как его легко не заметить. Например, вы можете добавить в int с и double с использованием той же записи x += 2 и компилятор выдает:

  • специфичные для типа инструкции процессора
  • результат того же типа.

Затем перегрузка плавно распространяется на пользовательские типы:

std::string x;
int y = 0;

x += 'c';
y += 'c';

Предоставляемые компилятором перегрузки для основных типов распространены в компьютерных языках высокого уровня (3GL+), и явное обсуждение полиморфизма обычно подразумевает нечто большее. (2GLs - языки ассемблера - часто требуют, чтобы программист явно использовал разные мнемоники для разных типов.)

> Стандартные преобразования

Четвертый раздел стандарта C++ описывает стандартные преобразования.

Первый пункт хорошо подводит итог (из старого проекта - надеюсь, все еще в значительной степени правильный):

-1- Стандартные преобразования - это неявные преобразования, определенные для встроенных типов. Статья conv перечисляет полный набор таких преобразований. Стандартная последовательность преобразования - это последовательность стандартных преобразований в следующем порядке:

  • Нулевое или одно преобразование из следующего набора: преобразование lvalue-в-значение, преобразование массива в указатель и преобразование функции в указатель.

  • Ноль или одно преобразование из следующего набора: интегральные преобразования, повышение с плавающей запятой, интегральные преобразования, преобразования с плавающей запятой, преобразования с плавающей запятой, преобразования указателя, преобразования указателя в член и логические преобразования.

  • Ноль или одна квалификация конверсии.

[Примечание: стандартная последовательность конверсий может быть пустой, т. Е. Не содержать конверсий. ] Стандартная последовательность преобразования будет применена к выражению, если необходимо преобразовать его в требуемый тип назначения.

Эти преобразования позволяют код, такой как:

double a(double x) { return x + 2; }

a(3.14);
a(42);

Применяя предыдущий тест:

Быть полиморфным, [ a() ] должен уметь работать со значениями как минимум двух разных типов (например, int а также double), поиск и выполнение кода, соответствующего типу.

a() сам запускает код специально для double и поэтому не является полиморфным.

Но во втором звонке a() компилятор знает, как сгенерировать соответствующий типу код для "продвижения с плавающей запятой" (стандарт §4) для преобразования 42 в 42.0, Этот дополнительный код находится в вызывающей функции. Мы обсудим значение этого в заключении.

> Принуждение, приведение, неявные конструкторы

Эти механизмы позволяют пользовательским классам определять поведение, схожее со стандартными преобразованиями встроенных типов. Давайте посмотрим:

int a, b;

if (std::cin >> a >> b)
    f(a, b);

Здесь объект std::cin оценивается в логическом контексте с помощью оператора преобразования. Это может быть концептуально сгруппировано с "интегральными поощрениями" и др. Из Стандартных конверсий в теме выше.

Неявные конструкторы фактически делают то же самое, но управляются типом приведения:

f(const std::string& x);
f("hello");  // invokes `std::string::string(const char*)`

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

Рассматривать:

void f()
{
    typedef int Amount;
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
}

Если мы хотим сумму x для деления на реальное число при делении (т. е. быть 6,5, а не округлено до 6), нам нужно только изменить на typedef double Amount,

Это хорошо, но это не было бы слишком много работы, чтобы сделать код явно "тип правильно":

void f()                               void f()
{                                      {
    typedef int Amount;                    typedef double Amount;
    Amount x = 13;                         Amount x = 13.0;
    x /= 2;                                x /= 2.0;
    std::cout << double(x) * 1.1;          std::cout << x * 1.1;
}                                      }

Но учтите, что мы можем преобразовать первую версию в template:

template <typename Amount>
void f()
{
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
}

Это из-за этих маленьких "удобных функций", которые могут быть легко созданы для любого из int или же double и работать как задумано. Без этих функций нам потребовались бы явные приведения, черты типов и / или классы политик, какой-то подробный, подверженный ошибкам беспорядок, такой как:

template <typename Amount, typename Policy>
void f()
{
    Amount x = Policy::thirteen;
    x /= static_cast<Amount>(2);
    std::cout << traits<Amount>::to_double(x) * 1.1;
}

Таким образом, предоставляемая компилятором перегрузка операторов для встроенных типов, стандартные преобразования, приведение / приведение / неявные конструкторы - все они вносят тонкий вклад в полиморфизм. Из определения в верхней части этого ответа они обращаются к "поиску и выполнению кода, соответствующего типу" путем сопоставления:

  • "подальше" от типов параметров

    • из многих типов данных полиморфный алгоритм алгоритмического кода

    • код, написанный для (потенциально меньшего) числа (того же или другого) типов.

  • "до" параметрических типов из значений постоянного типа

Они не устанавливают полиморфные контексты сами по себе, но помогают расширить возможности / упростить код в таких контекстах.

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

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

В C++ важное различие заключается во связывании во время выполнения и во время компиляции. Ad-hoc против параметрического не очень помогает, как я объясню позже.

|----------------------+--------------|
| Form                 | Resolved at  |
|----------------------+--------------|
| function overloading | compile-time |
| operator overloading | compile-time |
| templates            | compile-time |
| virtual methods      | run-time     |
|----------------------+--------------|

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

Перегрузка функций и перегрузка операторов - это одно и то же во всех отношениях. Имена и синтаксис их использования не влияют на полиморфизм.

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

Есть другой набор имен для той же идеи времени разрешения...

|---------------+--------------|
| early binding | compile-time |
| late binding  | run-time     |
|---------------+--------------|

Эти имена больше связаны с ООП, поэтому немного странно говорить, что шаблон или другая функция, не являющаяся членом, использует раннее связывание.

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

  • Во-первых, есть мономорфные функции. Реализация функции однозначно идентифицируется по имени функции. Ни один из параметров не является особенным.
  • Тогда есть единственная отправка. Один из параметров считается специальным и используется (вместе с именем) для определения, какую реализацию использовать. В ООП мы склонны считать этот параметр "объектом", перечислять его перед именем функции и т. Д.
  • Затем происходит многократная отправка. Любые / все параметры помогают определить, какую реализацию использовать. Поэтому, опять же, ни один из параметров не должен быть специальным.

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

Так же, как немного странно использовать термин "раннее связывание" для функций, не являющихся членами, немного странно использовать термины "одиночная отправка" и "множественная отправка", где полиморфизм разрешается во время компиляции. Обычно считается, что C++ не имеет многократной отправки, что считается определенным видом разрешения во время выполнения. Однако перегрузку функций можно рассматривать как множественную диспетчеризацию, выполняемую во время компиляции.

Возвращаясь к параметрическому и специальному полиморфизму, эти термины более популярны в функциональном программировании, и они не совсем работают в C++. Даже так...

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

Специальный полиморфизм является специальным в том смысле, что вы предоставляете различный код в зависимости от конкретных типов.

Перегрузка и виртуальные функции являются примерами специального полиморфизма.

Опять же, есть некоторые синонимы...

|------------+---------------|
| parametric | unconstrained |
| ad-hoc     | constrained   |
|------------+---------------|

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

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

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

Например, в Haskell, вы можете иметь...

myfunc1 :: Bool -> a -> a -> a
myfunc1 c x y = if c then x else y

a здесь неограниченный полиморфный тип. Это может быть что угодно, поэтому мы мало что можем сделать со значениями этого типа.

myfunc2 :: Num a => a -> a
myfunc2 x = x + 3

Вот, a вынужден быть членом Num класс - типы, которые действуют как числа. Это ограничение позволяет выполнять нумерацию с этими значениями, например добавлять их. Даже 3 является полиморфным - вывод типа выясняет, что вы имеете в виду 3 типа a,

Я считаю это ограниченным параметрическим полиморфизмом. Существует только одна реализация, но она может применяться только в ограниченных случаях. Специальным аспектом является выбор которого + а также 3 использовать. Каждый "экземпляр" Num имеет свою собственную реализацию этих. Так что даже в Хаскеле "параметрический" и "неограниченный" на самом деле не являются синонимами - не вините меня, это не моя вина!

В C++ как перегрузка, так и виртуальные функции являются специальным полиморфизмом. Определение специального полиморфизма не имеет значения, выбрана ли реализация во время выполнения или во время компиляции.

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

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

Что касается специального полиморфизма, это означает перегрузку функций или перегрузку операторов. Проверьте здесь:

http://en.wikipedia.org/wiki/Ad-hoc_polymorphism

Что касается параметрического полиморфизма, то функции шаблона также могут учитываться, поскольку они не обязательно принимают параметры типов FIXED. Например, одна функция может сортировать массив целых чисел, а также сортировать массив строк и т. Д.

http://en.wikipedia.org/wiki/Parametric_polymorphism

Это может не помочь, но я сделал это, чтобы познакомить моих друзей с программированием, выдавая определенные функции, такие как START, а также END для основной функции, так что это было не слишком сложно (они использовали только файл main.cpp). Он содержит полиморфные классы и структуры, шаблоны, векторы, массивы, директивы препроцессора, дружбу, операторы и указатели (все, что вы, вероятно, должны знать перед попыткой полиморфизма):

Примечание: это не закончено, но вы можете получить идею

main.cpp

#include "main.h"
#define ON_ERROR_CLEAR_SCREEN false
START
    Library MyLibrary;
    Book MyBook("My Book", "Me");
    MyBook.Summarize();
    MyBook += "Hello World";
    MyBook += "HI";
    MyBook.EditAuthor("Joe");
    MyBook.EditName("Hello Book");
    MyBook.Summarize();
    FixedBookCollection<FairyTale> FBooks("Fairytale Books");
    FairyTale MyTale("Tale", "Joe");
    FBooks += MyTale;
    BookCollection E("E");
    MyLibrary += E;
    MyLibrary += FBooks;
    MyLibrary.Summarize();
    MyLibrary -= FBooks;
    MyLibrary.Summarize();
    FixedSizeBookCollection<5> Collection("My Fixed Size Collection");
    /* Extension Work */ Book* Duplicate = MyLibrary.DuplicateBook(&MyBook);
    /* Extension Work */ Duplicate->Summarize();
END

main.h

#include <iostream>
#include <sstream>
#include <vector>
#include <string>
#include <type_traits>
#include <array>
#ifndef __cplusplus
#error Not C++
#endif
#define START int main(void)try{
#define END GET_ENTER_EXIT return(0);}catch(const std::exception& e){if(ON_ERROR_CLEAR_SCREEN){system("cls");}std::cerr << "Error: " << e.what() << std::endl; GET_ENTER_EXIT return (1);}
#define GET_ENTER_EXIT std::cout << "Press enter to exit" << std::endl; getchar();
class Book;
class Library;
typedef std::vector<const Book*> Books;
bool sContains(const std::string s, const char c){
    return (s.find(c) != std::string::npos);
}
bool approve(std::string s){
    return (!sContains(s, '#') && !sContains(s, '%') && !sContains(s, '~'));
}
template <class C> bool isBook(){
    return (typeid(C) == typeid(Book) || std::is_base_of<Book, C>());
}
template<class ClassToDuplicate> class DuplicatableClass{ 
public:
    ClassToDuplicate* Duplicate(ClassToDuplicate ToDuplicate){
        return new ClassToDuplicate(ToDuplicate);
    }
};
class Book : private DuplicatableClass<Book>{
friend class Library;
friend struct BookCollection;
public:
    Book(const char* Name, const char* Author) : name_(Name), author_(Author){}
    void operator+=(const char* Page){
        pages_.push_back(Page);
    }
    void EditAuthor(const char* AuthorName){
        if(approve(AuthorName)){
            author_ = AuthorName;
        }
        else{
            std::ostringstream errorMessage;
            errorMessage << "The author of the book " << name_ << " could not be changed as it was not approved";
            throw std::exception(errorMessage.str().c_str());
        }
    }
    void EditName(const char* Name){
        if(approve(Name)){
            name_ = Name;
        }
        else{
            std::ostringstream errorMessage;
            errorMessage << "The name of the book " << name_ << " could not be changed as it was not approved";
            throw std::exception(errorMessage.str().c_str());
        }
    }
    virtual void Summarize(){
        std::cout << "Book called " << name_ << "; written by " << author_ << ". Contains "
            << pages_.size() << ((pages_.size() == 1) ? " page:" : ((pages_.size() > 0) ? " pages:" : " pages")) << std::endl;
        if(pages_.size() > 0){
            ListPages(std::cout);
        }
    }
private:
    std::vector<const char*> pages_;
    const char* name_;
    const char* author_;
    void ListPages(std::ostream& output){
        for(int i = 0; i < pages_.size(); ++i){
            output << pages_[i] << std::endl;
        }
    }
};
class FairyTale : public Book{
public:
    FairyTale(const char* Name, const char* Author) : Book(Name, Author){}
};
struct BookCollection{
friend class Library;
    BookCollection(const char* Name) : name_(Name){}
    virtual void operator+=(const Book& Book)try{
        Collection.push_back(&Book); 
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
    virtual void operator-=(const Book& Book){
        for(int i = 0; i < Collection.size(); ++i){
            if(Collection[i] == &Book){
                Collection.erase(Collection.begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
private:
    const char* name_;
    Books Collection;
};
template<class FixedType> struct FixedBookCollection : public BookCollection{
    FixedBookCollection(const char* Name) : BookCollection(Name){
        if(!isBook<FixedType>()){
            std::ostringstream errorMessage;
            errorMessage << "The type " << typeid(FixedType).name() << " cannot be initialized as a FixedBookCollection";
            throw std::exception(errorMessage.str().c_str());
            delete this;
        }
    }
    void operator+=(const FixedType& Book)try{
        Collection.push_back(&Book); 
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
    void operator-=(const FixedType& Book){
        for(int i = 0; i < Collection.size(); ++i){
            if(Collection[i] == &Book){
                Collection.erase(Collection.begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
private:
    std::vector<const FixedType*> Collection;
};
template<size_t Size> struct FixedSizeBookCollection : private std::array<const Book*, Size>{
    FixedSizeBookCollection(const char* Name) : name_(Name){ if(Size < 1){ throw std::exception("A fixed size book collection cannot be smaller than 1"); currentPos = 0; } }
    void operator+=(const Book& Book)try{
        if(currentPos + 1 > Size){
            std::ostringstream errorMessage;
            errorMessage << "The FixedSizeBookCollection " << name_ << "'s size capacity has been overfilled";
            throw std::exception(errorMessage.str().c_str());
        }
        this->at(currentPos++) = &Book;
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
private:
    const char* name_;
    int currentPos;
};
class Library : private std::vector<const BookCollection*>{
public:
    void operator+=(const BookCollection& Collection){
        for(int i = 0; i < size(); ++i){
            if((*this)[i] == &Collection){
                std::ostringstream errorMessage;
                errorMessage << "The BookCollection " << Collection.name_ << " was already in the library, and therefore cannot be added";
                throw std::exception(errorMessage.str().c_str());
            }
        }
        push_back(&Collection);
    }
    void operator-=(const BookCollection& Collection){
        for(int i = 0; i < size(); ++i){
            if((*this)[i] == &Collection){
                erase(begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The BookCollection " << Collection.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
    Book* DuplicateBook(Book* Book)const{
        return (Book->Duplicate(*Book));
    }
    void Summarize(){
        std::cout << "Library, containing " << size() << ((size() == 1) ? " book collection:" : ((size() > 0) ? " book collections:" : " book collections")) << std::endl;
        if(size() > 0){
            for(int i = 0; i < size(); ++i){
                std::cout << (*this)[i]->name_ << std::endl;
            }
        }
    }
};

Полиморфизм - это способность объекта принимать разные формы. Наиболее распространенное использование полиморфизма в ООП происходит, когда ссылка на родительский класс используется для ссылки на объект дочернего класса. Любой объект Java, который может пройти более одного теста IS-A, считается полиморфным.

Вот основной пример использования полиморфных классов

#include <iostream>

class Animal{
public:
   Animal(const char* Name) : name_(Name){/* Add any method you would like to perform here*/
    virtual void Speak(){
        std::cout << "I am an animal called " << name_ << std::endl;
    }
    const char* name_;
};

class Dog : public Animal{
public:
    Dog(const char* Name) : Animal(Name) {/*...*/}
    void Speak(){
        std::cout << "I am a dog called " << name_ << std::endl;
    }
};

int main(void){
    Animal Bob("Bob");
    Dog Steve("Steve");
    Bob.Speak();
    Steve.Speak();
    //return (0);
}

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

Если кто-нибудь говорит, что CUT этим людям

The Surgeon
The Hair Stylist
The Actor

Что случится?

The Surgeon would begin to make an incision.
The Hair Stylist would begin to cut someone's hair.
The Actor would abruptly stop acting out of the current scene, awaiting directorial guidance.

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

Если вы идете на собеседование, и интервьюер попросит вас рассказать / показать пример из жизни в полимере в той же комнате, где мы сидим, скажем:

Ответ - Дверь / Окна

Хотите знать, как?

Через дверь / окно - может прийти человек, может прийти воздух, может прийти свет, может прийти дождь и т. Д.

т.е. одна форма отличается поведением (полиморфизм).

Чтобы понять это лучше и проще, я использовал приведенный выше пример. Если вам нужна ссылка на код, следуйте приведенным выше ответам.

Другие вопросы по тегам