/*************************************************/
/*多肽与继承的测试*/
/*************************************************/
#include <iostream>
using namespace std;
class A
{
public:
A() { cout<<"Create A"<<endl; }
virtual ~A() { cout<<"Destroy A"<<endl; }
virtual void Print() { cout<<"Print A"<<endl; }
}; // 注意这里要有分号,我就犯了这个错误。
class B : public A
{
public:
B() { cout<<"Create B"<<endl; }
~B() { cout<<"Destroy B"<<endl; }
virtual void Print() { cout<<"Print B"<<endl; }
};
int main(int argc, char* argv[])
{
B *a = new B(); // [1]或者 A *a = new B();
// a->Print(); // [2]
delete(a); // [3]
a = NULL; // [4]
system("pause");
return 1;
}
先看上面的代码:
在写下面的内容之前,我觉得首先需要提出:
学习时不要怕犯低级错误,每次犯错,都是一次积累。譬如,在上面代码里,我曾就在定义了class A和class B之后忘记了加分号,导致编译不通过,还找了一会儿才发现这个问题。再如,我在[1]这里创建对象时,曾经写成:
B b = new A();
这里一共犯了两个错误,这是JAVA的写法。
(1).在c++里面,new返回的是指针,所以B b应该是B *b才对。
(2).第二个错误,编译器会告诉我,不能将对象A转换成对象B,B继承了A,如果通过new A()当然不能创建对象B,只能通过B来创建A,可以这样来理解,因为B包括了A的所有东西,所以通过类A来创建B,必然会导致B的一些属性残缺。正确的应该是: A *a = new B();
我想要分享的重点在于以下三点:
1.关于子类和父类构造与虚构调用顺序一句话原则:
构造调用是由上至下(先父类后子类),析构的调用是由下至上(先子类后父类)。
2.虚析构的意义。
3.遇到不能理解的东西,最好的办法实践。
----------------------------------------------------------------------------------------------------------------------------------------------
运行上面的代码,得到的结果如下:
这验证了第一点:构造的调用顺序总是先调用基类,后调用派生类。对象由底层开始创建,由上层开始析构。所以析构的调用顺序和构造调用顺序是严格逆反的。
如果我们把代码里class A的析构前的 virtual 关键字去掉,再运行一下,结果是这样的:
没看错!没有发生变化。哈哈,别急,接下来,我们把代码里[1]这段代码替换一下:
B *a = new B(); ----> A *a = new B();
然后再运行一下:
这下不一样了!是的,派生类B的析构函数没有被调用!这样很可能出现:因为没有正确析构而导致内存泄露的问题。
我们再看看百度百科里,对于虚析构函数的介绍:
这里:A *a = new B(); a是指向基类A的指针,再析构时,只调用了基类的析构函数,但没有调用派生类B的析构函数,加入了关键字 virtual 后,才能保证只想基类的指针能正确被删除。
好了。这次想要分享的内容差不多就这些了。最后一条是我的切身体会,很多看不懂的东西,拿来实践一番,一下就明白了。希望这些东西能给大家带来帮助!
另外分享一点别人写的关于虚析构的说明,从实现上说明了原因:
虚函数和普通成员函数的区别,是虚函数放在虚函数表中,通过对象的this指针找到该类的虚函数表,然后调用。C++即采用此机制实现多态。如果是普通函数,每个函数的地址是死的。所以用A类的对象调用析构函数时只能调到A的析构。如果是虚函数,则会通过指针找到B的析构函数,而B继承自A,还会调用A的析构函数。
——摘自:http://blog.csdn.net/cffy625/article/details/5225064