看下面例子:
#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。
上面的例子我称为“反多态”,因为,通过一个多态的指针(多态时是父类型的指针,上面的例子是子类型的指针)访问普通成员函数时访问的是子类的函数(刚好和多态相反),通过子类的指针访问虚函数时访问的是父类的函数(也刚好和多态相反)。