Полиморфизм - это техника использования механизма позднего связывания и наследования.
Позднее связывание означает, что выбор конкретного типа переменной происходит во время работы программы, а не задается программистом явно.
Ну например, ты пишешь игру в которой есть огород и различные виды овощей - морковки, капусты и т.п. Юзер будет садить эти овощи в произвольном порядке, ты не знаешь порядок на этапе написания программы, поэтому ты не можешь написать что-то типа:
Carrot carrot_obj;
//...
carrot_obj->grow(); // просишь конкретную морковку подрасти
Есть разные способы извернуться и решить проблему в этой ситуации, но самый лаконичный из них - это полиморфизм.
Позднее связывание выполняет определение конкретного типа (вида овоща) на этапе выполнения, а на этапе компиляции ты должен связать все виды овощей с более общим типом. Поэтому полиморфизм - это наследование + позднее связывание. Наиболее общий класс
должен описывать интерфейс для всех наших овощей:
class Fruit {
protected:
int size;
public:
virtual int grow() = 0; // метод заставляющий абстрактный овощ расти
};
Именно через этот интерфейс при использовании полиморфизма мы обращаемся к конкретным классам овощей (выбор конкретного класса выполняется автоматически {программисту не надо для этого ничего делать} в зависимости от типа объекта, который скрыт за этим интерфейсом:
class Carrot : public Fruit {
public:
int grow() { return ++size; }
}
class Potato : public Fruit {
public:
int grow() { return size += 2; }
}
При таком раскладе, наш огород должен содержать список овощей (абстрактных):
vector<Fruit*> fruits;
при этом в С++ для использования позднего связывания надо применять указатели (или умные указатели если не хотим мучиться с утечками памяти), а в Java и С#, например, все будет сделано автоматически, т.к. там повсеместно используется идиома PIMPL (там всегда везде умные указатели).
Допустим, юзер перетащит морковку в свой огород. При этом будет создана новая морковка (каким-либо образом - я привел пример с явным вызовом конструктора, но это могло бы быть и что то типа шаблона Prototype {при этом не было бы указано что это морковка даже}) и добавлена в список овощей.
Fruit *obj = new Carrot();
fruits->push_back(obj);
Затем, в этот список могли бы добавляться в хаотичном порядке другие овощи, а какой-либо код с некоторой периодичностью выполнял рост этих овощей:
for(i = 0; i < fruits.size(); ++i) {
fruits[i]->grow();
}
При этом если i-тый объект массива фруктов был создан как морковка, то будет выполняться код Carrot::grow(), а если как картошка - то Potato::grow(). Программисту теперь не надо париться с этим, функция нужного типа будет посдавлена автоматически во время выполнения.