看下面例子:

#include "stdafx.h"

#include <iostream>

class A {  //父类

public:  

  void  f()   //普通函数

  {   

    std::cout<<"A"<<std::endl;  

  }  

  void virtual vf()   //虚函数

  {   

    std::cout<<"virtual-A"<<std::endl;  }

  };

class B : public A  //子类

{

public:  

  void  f()  

  {  

    std::cout<<"B"<<std::endl;  

  }  

  void virtual vf()  

  {   

    std::cout<<"virtual-B"<<std::endl;  

  }

   int m;  //B类的成员变量  

  B()    //在B类的构造中给成员变量赋值  

  {   

    this->m=20;  

  }

};

int _tmain(int argc, _TCHAR* argv[]) 

{  

  A *pa = new A();  //只实例化一个父类的对象,但下面要当作子类用  

  B *pb = (B *)pa; //用强制类型转换,把父类指针转化为子类指针,用这个子类指针访问子类的成员  

  pb->f();    //覆盖时,调用的是子类的函数  

  pb->vf();  //虚函数时,调用的是父类的函数,是因为虚函数表记录的是父类的方法,没有显示new一个子类,所以不会改虚函数表的内容  

  std::cout<<pb->m<<std::endl;  //结果输出的是乱码,不是20  

  //原因,要输出子类的成员变量m,但实际上pb指向的内存空间只有父类大小,所以访问的m实际上是超出了实例化的对象的范围  return 0;

}

运行结果:

总结:其实可以这样理解,根据某个类型的指针访问某个成员时,编译器会根据类型的定义, 查找这个成员(对编译器来讲成员名只是个标记)所在偏移量,用这个偏移量获取成员。 比如上面用指针pb访问成员变量m时,m对于pb来说只是个标记,说明要从pb开始偏移多少个字节才能访问到m。

上面的例子我称为“反多态”,因为,通过一个多态的指针(多态时是父类型的指针,上面的例子是子类型的指针)访问普通成员函数时访问的是子类的函数(刚好和多态相反),通过子类的指针访问虚函数时访问的是父类的函数(也刚好和多态相反)。