A virtual function (more properly, a virtual method, although that terminology is almost never used) is a member function called with dynamic binding. Some languages (e.g. Smalltalk) offer only dynamic binding, and so don't use the term. It's chiefly found by people talking about C++ and related languages, where both static binding and dynamic binding co-exist.

Normally, C++ provides static binding: the compiler figures out (at compile time) which method should be called. For example, say we have code

class Shape {
public:
  void draw() const;  // slow drawing routine
  void move(int sx, sy) {
    cx += sx;
    cy += sy;
  }
  // ...
private:
  int cx, cy;         // coordinates of centre
};

class Triangle : public Shape {
public:
  void draw() const;  // fast specialised drawing routine
  // ...
};

class Square : public Shape {
public:
  void draw() const;  // fast specialised drawing routine
  // ...
};
The intent is that a Shape is abstract: while it makes sense to move (translate) it, it cannot be drawn without knowing some more about it.

The C++ compiler will resolve all method calls for Shapes at compile time, giving the following behaviour:

void f(Square& s, Triangle *t)
{
  s.move(100,100);              // (1) call Shape::move
  t->move(-50,-50);             // (2) call Shape::move
  s.draw();                     // (3) call Square::draw
  t->draw();                    // (4) call Triangle::draw
  Shape *p = &s;
  p->move(-100,-100);           // (5) call Shape::move; move s back
  p->draw();                    // (6) call Shape::draw; *slow*
  p = t;
  p->draw();                    // (7) call Shape::draw; *slow*
}
Static binding means the compiler determines the method to call based on the static types. If you're calling a method with static binding via a pointer to an object of type Shape, the compiler calls the method from type Shape.

This is distinct from what would happen if you said

  Square& s(construct,a,square);
  Shape q = s;
  q.draw();
In this case, object slicing occurs: what q holds is no longer a Square, just a Shape. When assigning pointers, you end up with a Shape pointer pointing to a Square. But lacking other instructions, the compiler still binds all methods at compile time, therefore to the methods of Shape. Virtual functions only apply when calling methods through a pointer or a reference, not directly from an object of the type!

In our example above, the behaviour was probably not intended. An alternative is to request the compiler to use dynamic binding to find the draw method for pointers:

class Shape {
public:
  virtual void draw() const;  // slow drawing routine
// ...
At run time, the object pointed to will be examined, and the appropriate method called. So at points (6) and (7) of executing f(), the dynamic type of the pointer is deduced, and the appropriate fast method executed.

A class is polymorphic if it has virtual functions or inherits from a class with virtual functions. The compiler must generate code for pointers to objects of a polymorphic class which can identify the type of the object and dispatch virtual functions to the appropriate function.

One way to implement virtual functions would be to attach to every object of polymorphic class a unique identifier (the compiler will generally hide this somewhere in the class' memory layout). For instance, if we attach the identifiers 100 to Shape, 101 to Square and 102 to Triangle, we can determine at run time which method to call at (6) and (7).

In practice, the popular implementation technique for virtual functions uses a vtable (or vtbl): every polymorphic class gets a table of function pointers to the appropriate method to call for each virtual function; the identifier attached to an object of polymorphic class is just a pointer to the vtable.

But there is no requirement to implement things in this manner! Indeed, given the example above, and no further inheritance from classes Square and Triangle anywhere in the whole program, a smart enough compiler could figure out the correct binding at compile time, and avoid the whole trouble of determining dynamic types at run time. By the "as if" rule, such an implementation would be legal. However, nothing near such sophistication appears to exist today.