转载自iyjhabc的博客,讲的十分通俗易懂,转载到自己的博客方便自己日后复习
首先我们结合一段示例代码来看
class A
{
public:
A()
{
puts("In A");
}
~A()
{
puts("Out A");
}
};
class B
{
public:
B()
{
puts("In B");
}
~B()
{
puts("Out B");
}
};
class D
{
public:
D()
{
puts("In D");
}
~D()
{
puts("Out D");
}
};
class X
{
public:
X()
{
puts("In X");
}
virtual ~X()
{
puts("Out X");
}
virtual void test()
{
puts("test in X");
}
D d;
};
class C:public X
{
public:
C()/*: b(), a()*/
{
puts("In C");
}
~C()
{
puts("Out C");
}
virtual void test()
{
puts("test in C");
}
private:
A a;
B b;
};
int main()
{
X* p = new C;
p->test();
delete p;
return 0;
}
在这里,类C公有继承类X,并且其中组合了类A和B的对象实例a和b。类X组合了类D的对象d,同时C和D在继承关系的同时,声明了虚函数test(涉及到多态的实现),X的析构函数声明为虚函数,后面会有讲解为何这么做。
new C的时候,由于C由X继承而来,因此先构造X。首先,按照初始化列表初始化X的成员变量,这里没有初始化列表,系统也会默认地为d进行默认初始化,此时调用D的构造函数,打印In D。(先初始化成员,再运行构造函数)
X的初始化阶段结束后,运行X的构造函数,打印In X。
X部分构造结束后,开始构造C的部分。同样的先进行初始化工作,无论有无初始化列表,无论初始化列表的顺序如何,成员的初始化顺序都按声明顺序进行初始化。打印In A,In B。
C的成员完成初始化后,调用C的构造函数,打印In C。
/----------------------------------至此,new C的过程完毕-----------------------------------------/
基类指针调用虚函数,进行动态绑定,输出test in C
/----------------------------------开始对指针p析构-----------------------------------------/
析构的顺序就是构造顺序的逆序。就是先析构子类,再析构父类(这里原博客说反了,感觉有失偏颇)。先析构本类,再析构本类的成员。
于是打印的顺序为O C, O B, O A, O X, O D
这个例子同时也解释了,为什么基类的析构函数要声明为虚函数。如果
//不是虚函数
~X()
{
puts("Out X");
}
那么delete p时候会直接析构p的静态类型X,所以C的成员以及C的析构函数将不会运行,这样就会造成内存泄露。