假设有以下三个类,是继承关系:
 1 class A()
class A()
 2

 {
{
 3 public:
public:
 4 int a;        //用于测试类的大小
    int a;        //用于测试类的大小
 5 public:
public:
 6 A()            //用于测试构造函数的调用顺序及default
    A()            //用于测试构造函数的调用顺序及default
 7 //constructor的必要性
                //constructor的必要性
 8
 
     {
{
 9 cout<<"A Constructor!"
        cout<<"A Constructor!"
10 };
    };
11
12 f()            //用于测试虚函数的特性,此处并不是虚函数。注意!
    f()            //用于测试虚函数的特性,此处并不是虚函数。注意!
13
 
     {
{
14 cout<<"f of A"
        cout<<"f of A"
15 };
    };
16 };
};
17
18 class B() : public A
class B() : public A
19

 {
{
20 public:
public:
21 int B;        //用于测试类的大小
    int B;        //用于测试类的大小
22 public:
public:
23 B()            //用于测试构造函数的调用顺序及default
    B()            //用于测试构造函数的调用顺序及default
24 //constructor的必要性
                //constructor的必要性
25
 
     {
{
26 cout<<"B Constructor!"
        cout<<"B Constructor!"
27 };
    };
28
29 virtual f()            //用于测试虚函数的特性,这里是虚函数
    virtual f()            //用于测试虚函数的特性,这里是虚函数
30
 
     {
{
31 cout<<"f of B"
        cout<<"f of B"
32 };
    };
33 };
};
34
35 class C()
class C()
36

 {
{
37 public:
public:
38 int c;        //用于测试类的大小
    int c;        //用于测试类的大小
39 public:
public:
40 C()            //用于测试构造函数的调用顺序及default
    C()            //用于测试构造函数的调用顺序及default
41 //constructor的必要性
                //constructor的必要性
42
 
     {
{
43 cout<<"C Constructor!"
        cout<<"C Constructor!"
44 };
    };
45
46 virtual f()            //用于测试虚函数的特性
    virtual f()            //用于测试虚函数的特性
47
 
     {
{
48 cout<<"f of C"
        cout<<"f of C"
49 };
    };
50 };
};
51
 
几点结论:
 1.构造函数调用:基类必需有无参的默认构造函数。否则派生类的构造函数无法能过编译。
   提示:no appropriate default consturctor.
 2.当new一个派生类时,如C,会依次调用所有父类的默认拷贝构造函数。
  测试代码:
   
    A* a = new c;
  输出结果为:依次调用三个构造函数。
 3.用基类指针指向派生类时,基类指针所指向对象的大小,变化为基类的大小。即由指针类型来决定的。
  测试代码1:
   C objectC;
   A* pA;
   B* pB;
   C* pC;
   pA = &objectC;
   pB = &objectC;
   pC = &objectC;
   cout<<"sizeof(*pA)"<<endl;
   cout<<"sizeof(*pB)"<<endl;
   cout<<"sizeof(*pC)"<<endl;
   这时输出的结果为:
   4  //一个int成员,大小为4字节
   12 //二个int,另外加上一个虚函数表指针,大小为12字节
   16 //三个int,另外加上一个虚函数表指针,大小为16字节
  测试代码2:
   A* pA = new C;
   B* pB = new C;
   C* pB = new C;
   cout<<"sizeof(*pA)"<<endl;
   cout<<"sizeof(*pB)"<<endl;
   cout<<"sizeof(*pC)"<<endl;
   输出结果同上。
 4.将基类指针指向派生类对象的操作,是安全的,也是常见的,如用CShape指针指向CRect,CCircle,COval,CTriangle对象。
 5.将派生类指针转化为基类指针,如:
   pC = new C;
   A* pA = pC;
   B* pB = pC;
   或者:
   A* pA = new C;
   B* pB = new C;
   同样是安全的,也是常见的。
  但是,在本文的三个类中,由于类A并没有虚函数,所以情况有一些特殊:
   1.pA->f(); //输出结果为:f of A.
   这仅仅是因为A类中恰好有同名函数f,若将该函数改名为fa(),
   则编译会出现错误:
    f() is not a member function of classs A...
   这说明,转换后,由于本身类A无虚函数,所以编译器并没有为其构造虚函数表,也没有为其加上虚函数表指针。所以
   不能寻找到函数f。即,该类没有多态能力。
   
   2.pB->f(); //输出结果为:f of C
   因为B本身也是拥有虚函数的类,所以,编译器也为其构造了虚函数表,并在类中加上虚函数表指针变量。而虚函数表中
   的内容,同类C中的虚函数表是一致的。所以,可以正确找到要调用的函数。
  需要注意的是,因为第三条的缘故,此时若
   cout<<sizeof(*pA); 
   cout<<sizeof(*pB); 
  得到的结果依然是4,12。
  这从另一个角度说明了,类的函数在内存中有单独的位置,与C语言中的函数没有差别。——并不占用类的内存空间,也不受类
  的影响。
 6.将派生类对象转换为基类对象,则会发生一些潜在的危险。这里只从虚函数的调用来说明:
    测试代码:
    C testC;
    ((A*)(&testC))->f(); //输出结果为:f of A
    ((B*)(&testC))->f(); //输出结果为:f of C
    ((B)(testC)).f();  //输出结果为:f of B
   1.第一行的结果,同上一条的分析是一致的:将派生类的指针转换成了基类指针。由于A类并没有虚函数,因此没有虚函数表
   和虚函数表指针,所以只能调用自己所拥有的、“名为f”的、函数。
   2.第二行的结果,同上一条的分析是一致的:将派生类的指针转成了基类指针。但B类有虚函数,因此有虚函数表和虚函数表
   指针,所以可以正确调用到对象testC,即类C中的f()函数。
   3.第三行的结果,是因为将一个C类的testC对象转换成一个B类的对象,产生了称之为object
   slicing 的操作。
   这基于一个通常来说正确的结论:一个派生类的内存空间要比基类的内存空间要大。
   (因为派生类通常会拥有基类没有的数据。如在这里,类C显然要比类A大。一个16,一个4。)
   因此强制转换为将testC“割”掉一部分,然后编译器会自动调用的类B的拷贝构造函数,
   来给testC此时的各项赋值。在这个过程中,虚函数表也被换成了类B的虚函数表。
   所以,此时再调用f(),显然要调用B的f()函数。
   4.对比第二行和第三行的结果,非常有意思:
     将派生类指针转换为基类指针
       与
     将派生类对象转换为基类对象
    会导致不同的虚函数调用结果。