Ключ к пониманию практического использования виртуальных функций заключается в том, чтобы помнить, что объекту определенного класса может быть присвоен другой объект класса, производный от класса первого объекта.
Например:
class Animal {
public void eat() {...}
}
class FlyingAnimal : Animal {
public void eat() {...}
}
Animal a = new FlyingAnimal();
Класс Animal
имеет функцию eat()
, которая обычно описывает, как животное должно питаться (например, помещать предмет в рот и глотать).
Однако класс FlyingAnimal
должен определять новый метод eat()
, потому что летающие животные имеют особый способ питания.
Таким образом, возникает вопрос: после того, как я объявил переменную a
типа Animal
и присвоил ей новый объект типа FlyingAnimal
, что будет делать a.eat()
? Какой из двух методов называется?
Ответ здесь: поскольку a
имеет тип Animal
, он вызовет метод Animal
. Компилятор туп и не знает, что вы собираетесь назначить объект другого класса переменной a
.
Вот здесь и вступает в действие ключевое слово virtual
: если вы объявляете метод как virtual void eat() {...}
, вы, в основном, говорите компилятору: «будьте осторожны, я делаю некоторые умные вещи, которые вы не можете обработать, потому что не такой умный ". Таким образом, компилятор не будет пытаться связать вызов a.eat()
ни с одним из двух методов, но вместо этого он скажет системе сделать это во время выполнения !
Таким образом, только при выполнении кода система будет смотреть на a
тип контента , а не на объявленный тип и выполняет метод FlyingAnimal
.
Вы можете задаться вопросом: какого черта я хочу это сделать? Почему бы не сказать прямо с самого начала FlyingAnimal a = new FlyingAnimal()
?
Причина этого в том, что, например, у вас может быть много производных классов от Animal
: FlyingAnimal
, SwimmingAnimal
, BigAnimal
, WhiteDog
и т. Д. И в какой-то момент вы хотите определить мир, содержащий много Animal
с, так что вы говорите:
Animal[] happy_friends = new Animal[100];
У нас есть мир со 100 счастливыми животными.
Вы инициализируете их в какой-то момент:
...
happy_friends[2] = new AngryFish();
...
happy_friends[10] = new LoudSnake();
...
И в конце дня вы хотите, чтобы все съели перед сном. Итак, вы хотите сказать:
for (int i=0; i<100; i++) {
happy_friends[i].eat();
}
Итак, как вы можете видеть, у каждого животного есть свой метод питания. Только используя виртуальные функции, вы можете достичь этой функциональности. В противном случае каждый был бы вынужден «есть» точно таким же образом: как описано в самой общей функции eat
внутри класса Animal
.
EDIT:
Такое поведение на самом деле по умолчанию в распространенных языках высокого уровня, таких как Java.