Сложные для понимания технологии хорошо усваиваются на реальных примерах.

Если хочешь понять как что-то работает сделай это что-то сам.
С абстрактнами классами или виртуальными функциями надо сначала понять когда их удобно использовать.
Так вот момент истины наступает когда ваш проект начинает разрастаться вширь и вглубь...
Ну например вы подключили к своему проекту какое-то оборудование. Настроили управление им.
Потом решили подключить другое оборудование и поняли , что оно как и предыдущее оборудование управляется однотипно, то есть как бы можно создать набор функций (интерфейс) и через них управлять однотипно обоими вариантами оборудования.
Вот тут и надо создать сначала абстрактный класс, в нем прописать наш набор функций с типом virtual и унаследовать оба наших оборудования от этого абстрактного класса.
В классе ,унаследованном от абстрактного класса, надо конечно реализовать эти функции применительно к каждому конкретному оборудованию.
В чем тут выгода? В том что теперь можно работать с каждым из вариантов оборудования единообразно через вызовы методов из набора в абстрактном классе.
А вызываться на самом деле будет функция конкретного класса (оборудования).
Но и это еще не все.
Допустим у вас есть абстрактный класс A (уровень 0), далее имеем унаследованный от него класс A1 (1 уровень).
Далее предположим , что класс A1 уже многократно используется в вашем коде и менять его уже очень не хочется. Так сказать он самодостаточен и логичен.
И есть предположим в классе A1 некая функция A1::foo, в которой вы сохраняете куда-то какие-то данные.
Но вот вам еще захотелось добавить некий дополнительный контроль сохраняемых данных.
Чтобы не менять класс A1 можно просто объявить функцию foo виртуальной .
Далее унаследоваться от класса A1, то есть создать класс A2 (уровень 2) , где создать новую реализацию функции foo, в которой сделать сначала проверку записываемых данных, а потом вызвать функцию A1::func.
Таким образом вы не меняете реализацию класса A1. И получаете класс A2 расширяюший функциональность класса A1.
В коде вы продолжаете многократно использовать класс A1 и по необходимости добавляете где-надо использование класса A2.
И так далее ....
В итоге у вас получается грамотная иерархическая единственно правильная схема разделения функциональности и наследования классов.