C++ 程序员必经之路 —— 构造、析构、虚析构
2014-02-07 16:15
225 查看
/*************************************************/ /*多肽与继承的测试*/ /*************************************************/ #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
相关文章推荐
- Effective C++ Item 9 绝不在构造和析构过程中调用virtual函数
- C++ 构造、析构的顺序
- C++入门学习:继承中的构造和析构以及同名成员情况
- C++学习笔记--继承中的构造与析构
- C++对象的构造、赋值和析构
- C++构造与析构(5) - 何时必须自定义拷贝构造函数
- C++构造与析构(12) - copy elision编译器优化
- Effective C++ Item 9 绝不在构造和析构过程中调用virtual函数
- c++构造与析构
- C++构造和析构执行顺序
- 请用c++ 实现stl中的string类,实现构造,拷贝构造,析构,赋值,比较,字符串相加,获取长度及子串等功能
- 用汇编的眼光看C++(之class构造、析构)
- c++中,当异常遇见构造与析构
- C++继承中的构造与析构
- C++之类的构造与析构(一)
- C++构造与析构(7) - 数据成员的初始化
- C++构造与析构(13) - 内建类型的默认构造函数
- 05_c++构造和析构
- C++ 继承的构造与析构
- C++ 绝不在构造和析构过程中调用virtual函数