Вы получаете гипотетические ответы на эти вопросы, поэтому я попытаюсь сделать более простое, более приземленное объяснение для ясности.
Основные отношения объектно-ориентированного дизайна являются двумя:
IS-A и HAS-A. Я не сделал это. Вот как они называются.
IS-A указывает, что определенный объект идентифицируется как принадлежащий к классу, который находится над ним в иерархии классов. Банановый объект - это фруктовый объект, если он является подклассом фруктового класса. Это означает, что везде, где можно использовать фруктовый класс, можно использовать банан. Это не рефлексивно, хотя. Вы не можете заменить базовый класс для определенного класса, если этот конкретный класс вызывается.
Имеет-указывает, что объект является частью составного класса и что существуют отношения собственности. В C ++ это означает, что это объект-член, и поэтому ответственность за его уничтожение или передачу права собственности перед уничтожением себя лежит на классе-владельце.
Эти два понятия легче реализовать в языках с одним наследованием, чем в модели множественного наследования, такой как c ++, но правила по сути одинаковы. Сложность возникает, когда идентичность класса неоднозначна, например, передача указателя на класс Banana в функцию, которая принимает указатель на класс Fruit.
Виртуальные функции - это, во-первых, вещь во время выполнения. Он является частью полиморфизма в том смысле, что он используется для определения, какую функцию запускать во время ее вызова в работающей программе.
Виртуальное ключевое слово - это директива компилятора, связывающая функции в определенном порядке, если существует неопределенность в отношении идентичности класса. Виртуальные функции всегда находятся в родительских классах (насколько я знаю) и указывают компилятору, что связывание функций-членов с их именами должно осуществляться сначала с помощью функции подкласса, а после - с функцией родительского класса.
Класс Fruit может иметь виртуальную функцию color (), которая по умолчанию возвращает «NONE».
Функция color () класса Banana возвращает «ЖЕЛТЫЙ» или «КОРИЧНЕВЫЙ».
Но если функция, принимающая указатель 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 () своей собственной правильной функцией.
Я надеюсь, что это многословное объяснение укрепляет концепцию, а не путает вещи. Есть много хороших примеров, чтобы посмотреть,
и посмотри на достаточно, и на самом деле запусти их, и связывайся с ними, и ты получишь это.