Когда вы объявляете функцию виртуальной, вы на самом деле говорите компилятору, что хотите, чтобы эта функция работала полиморфно. То есть из вашего примера, если у нас есть следующее:
A* foo = new B();
foo->f();
он будет вызывать функцию "f" в B, а не функцию "f" в A. Если продолжить, если у нас есть C, который наследует от B, как вы сказали:
class C : public B{}
B* foo = new C();
foo->f():
это вызывает B's "f". Если бы вы определили его в C, он вызвал бы метод C.
Чтобы объяснить различное поведение между виртуальным и не виртуальным, давайте возьмем этот пример:
struct Foo{
virtual void f();
void g();
};
struct Bar{
virtual void f();
void g();
};
Foo* var = new Bar();
var->f(); //calls Bar's f
var->g(); //calls Foo's g, it's not virtual
имеет смысл?