输出结果:
A::fA()
B::vfA()
A::emptyB()
A::vfAonly()
A::fA()
B::fB()
B::vfA()
B::vfB()
B::emptyA()
A::emptyB()
B::vfAonly()

  分析:
  我们通过模拟编译器的编译过程来进行解释。只看编译器是怎么编译带有标号的那些函数调用的行的。
  行1. 在编译器眼中,p是一个纯粹的A类指针,跟他指向的B类对象没有任何联系。因此,当看到p->fA()时,编译器便去A的定义中寻找fA,找到了,于是生成调用代码。
  行2. 这行如果不被注释,编译器去A的定义中寻找定义fB,但是找不到这个名字,便会输出错误信息。
  行3. 编译器继续去A定义中寻找vfA,这次找到了,而且发现关键字virtual,于是,采用虚拟函数调用代码生成技术,根据vfA的偏移值,生成代码调用虚拟函数表中该偏移值指向的函数。特别指出的是,在静态编译期间,编译器只知道偏移值,并不知道运行时该偏移到底指向什么函数。实际效果是,因为运行时,p指向的是B对象,因此调用的是B的虚拟函数vfA().
  行4. 这行如果不被注释,编译器去A的定义中寻找名字vfB,找不到,出错。记住第一条原则,编译器是静态编译,不知道p和类B有联系。
  行5. 同4,找不到名字emptyA。
  行6. 简单,找到名字emptyB.
  行7. 简单,找到名字vfAonly。
  行8. 从这里开始,函数由B类引用r调用。在编译器眼中,r是一个纯粹的B类引用,他不假设r和A有任何关系。因此这一行,编译器去B类定义寻找名字fA。由于B继承自A,包括所有A的public函数定义,编译器成功找到A::fA。
  行9. 类似行8,找到B自身的函数定义fB。
  行10. 类似行3,编译器生成代码调用虚拟函数表某偏移指向的函数。运行时该偏移指向B::vfA.
  行11. 编译器生成代码调用虚拟函数表某偏移指向的函数。运行时该偏移指向B::vfB.
  行12. 简单,找到名字emptyA.
  行13. 简单,找到名字A::emptyB. 因为B继承自A。
  行14. 编译器生成代码调用虚拟函数表某偏移指向的函数。运行时该偏移指向B::vfAonly. 为什么编译器知道指向的是B的虚拟函数vfAonly而不是A的非虚拟函数呢?这跟另一个静态编译规则,名字隐藏,有关。
  继承类的作用域中如果有基类的同名函数,继承类中的名字将隐藏基类同名函数,因此这时,编译器看不见A::vfAonly。