Вы получаете гипотезы с этими ответами, поэтому я попытаюсь сделать более простое, более приземленное объяснение для ясности.
Основными отношениями объектно-ориентированного проектирования являются два: IS-A и HAS-A. Я не сделал это. Вот как они называются.
IS-A указывает, что определенный объект идентифицируется как принадлежащий к классу, который находится над ним в иерархии классов. Банановый объект - это фруктовый объект, если он является подклассом фруктового класса. Это означает, что везде, где можно использовать фруктовый класс, можно использовать банан. Это не рефлексивно, хотя. Вы не можете заменить базовый класс для определенного класса, если этот конкретный класс вызывается.
Has-a указывает, что объект является частью составного класса и что существуют отношения собственности. В C ++ это означает, что это объект-член, и поэтому ответственность за его ликвидацию или передачу прав собственности перед уничтожением себя лежит на классе-владельце.
Эти два понятия легче реализовать в языках с одним наследованием, чем в модели множественного наследования, такой как c ++, но правила по сути одинаковы. Сложность возникает, когда идентичность класса неоднозначна, например, передача указателя класса Banana в функцию, которая принимает указатель класса Fruit.
Виртуальные функции - это, во-первых, вещь во время выполнения. Он является частью полиморфизма в том смысле, что он используется для определения, какую функцию запускать во время вызова в запущенной программе.
Ключевое слово virtual - это директива компилятора, связывающая функции в определенном порядке, если существует неопределенность в отношении идентичности класса. Виртуальные функции всегда находятся в родительских классах (насколько я знаю) и указывают компилятору, что связывание функций-членов с их именами должно выполняться сначала с помощью функции подкласса, а после - с функцией родительского класса.
Класс Fruit может иметь виртуальную функцию color (), которая по умолчанию возвращает «NONE». Функция класса Banana color () возвращает «ЖЕЛТЫЙ» или «КОРИЧНЕВЫЙ».
Но если функция, принимающая указатель Fruit, вызывает color () для отправленного ей класса Banana - какая функция color () вызывается? Функция обычно вызывает Fruit :: color () для объекта Fruit.
Это будет 99% времени не быть тем, что предполагалось. Но если Fruit :: color () был объявлен виртуальным, тогда Banana: color () будет вызван для объекта, потому что правильная функция color () будет связана с указателем Fruit во время вызова. Среда выполнения проверит, на какой объект указывает указатель, поскольку он был помечен как виртуальный в определении класса Fruit.
Это отличается от переопределения функции в подклассе. В этом случае указатель Fruit будет вызывать Fruit :: color (), если все, что он знает, это IS-A указатель на Fruit.
Так что теперь к идее «чисто виртуальной функции» подходит. Это довольно неудачная фраза, поскольку чистота не имеет к этому никакого отношения. Это означает, что предполагается, что метод базового класса никогда не будет вызываться. Действительно чисто виртуальная функция не может быть вызвана. Это все еще должно быть определено, как бы то ни было. Подпись функции должна существовать. Многие кодеры создают пустую реализацию {} для полноты, но компилятор сгенерирует ее внутренне, если нет. В том случае, когда функция вызывается, даже если указатель указывает на Fruit, Banana :: color () будет вызвана, так как это единственная имеющаяся реализация color ().
Теперь последний кусок головоломки: конструкторы и деструкторы.
Чистые виртуальные конструкторы полностью запрещены. Это только что вышло.
Но чисто виртуальные деструкторы работают в том случае, если вы хотите запретить создание экземпляра базового класса. Только подклассы могут быть созданы, если деструктор базового класса является чисто виртуальным. соглашение состоит в том, чтобы присвоить это 0.
virtual ~Fruit() = 0; // pure virtual
Fruit::~Fruit(){} // destructor implementation
Вы должны создать реализацию в этом случае. Компилятор знает, что это то, что вы делаете, и удостоверяется, что вы все делаете правильно, или он сильно жалуется, что не может ссылаться на все функции, необходимые для компиляции. Ошибки могут сбивать с толку, если вы не на правильном пути относительно того, как вы моделируете иерархию классов.
Так что вам запрещено в этом случае создавать экземпляры Fruit, но разрешено создавать экземпляры Banana.
Вызов для удаления указателя Fruit, который указывает на экземпляр Banana, сначала вызовет Banana :: ~ Banana (), а затем всегда вызовет Fuit :: ~ Fruit (). Потому что, несмотря ни на что, когда вы вызываете деструктор подкласса, деструктор базового класса должен следовать.
Это плохая модель? Да, это более сложно на этапе проектирования, но оно может гарантировать, что правильное связывание выполняется во время выполнения и что функция подкласса выполняется там, где существует неопределенность в отношении того, к какому именно подклассу осуществляется доступ.
Если вы пишете C ++ так, что вы передаете только точные указатели классов без общих или неоднозначных указателей, то виртуальные функции на самом деле не нужны. Но если вам требуется гибкость типов во время выполнения (как в Apple Banana Orange ==> Fruit), функции становятся проще и универсальнее с меньшим количеством избыточного кода. Вам больше не нужно писать функцию для каждого типа фруктов, и вы знаете, что каждый фрукт будет реагировать на color () своей собственной правильной функцией.
Я надеюсь, что это многословное объяснение укрепляет концепцию, а не путает вещи. Есть много хороших примеров, на которые можно посмотреть, посмотреть достаточно, запустить их, поработать с ними, и вы получите это.