1.new 调用类的构造函数,delete调用析构函数
-
#include<iostream>
-
using namespace std;
-
class Base
-
{
-
public:
-
Base(){
-
cout<<"Base 构造函数"<<endl;
-
};
-
~Base()
-
{
-
cout<<"Base 析构函数"<<endl;
-
};
-
};
-
-
int main()
-
{
-
Base* base = new Base();
-
delete base;
-
cout<<"Middle"<<endl;
-
return 0;
-
}
程序输出:
Base 构造函数
Base 析构函数
Middle
若main函数改为
-
int main()
-
{
-
Base base;
-
cout<<"Middle"<<endl;
-
return 0;
-
}
输出:
Base 构造函数
Middle
Base 析构函数
在退出main函数的时候会自动调用Base的析构函数。
2.继承与析构函数
新建继承类时,先调用基类的构造函数,后调用继承类的构造函数;析构时,先调用继承类的析构函数,后调用基类的析构函数。
-
#include<iostream>
-
-
using namespace std;
-
-
class Base
-
{
-
public:
-
Base(){
-
cout<<"Base 构造函数"<<endl;
-
};
-
~Base()
-
{
-
cout<<"Base 析构函数"<<endl;
-
};
-
};
-
class Drived: public Base
-
{
-
public:
-
Drived()
-
{
-
cout<<"Drived 构造函数"<<endl;
-
};
-
~Drived()
-
{
-
cout<<"Drived 析构函数"<<endl;
-
};
-
};
-
int main()
-
{
-
Drived* a = new Drived;
-
delete a;
-
return 0;
-
}
程序输出:
Base 构造函数
Drived 构造函数
Drived 析构函数
Base 析构函数
其他情况:
-
int main()
-
{
-
Drived a;
-
Base* b = &a;
-
return 0;
-
}
程序输出:
Base 构造函数
Drived 构造函数
Drived 析构函数
Base 析构函数
b只是指向了a的指针,并不会对a的析构产生影响。
考虑下面的程序
-
int main()
-
{
-
Base* b = new Drived;
-
delete b;
-
return 0;
-
}
函数输出:
Base 构造函数
Drived 构造函数
Base 析构函数
函数在new Drived时会先调用基类的构造函数,后调用继承类的构造函数;在delete b时应为b为Base* 类型,所以会调用Base的析构函数但是不会调用继承类的析构函数。
这种情况是程序不希望看到的,因为继承类的构造函数申请的空间如果没有析构函数来释放,会对程序造成很大影响。这个时候就需要用到了虚函数。
思考
-
int main()
-
{
-
Drived d;
-
Base a = d;
-
return 0;
-
}
输出什么?
Base 构造函数
Drived 构造函数
Base 析构函数
Drived 析构函数
Base 析构函数
变量d的构造和析构符合正常行为,a变量没有打印出来构造函数,却调用了析构函数,可以理解为d完成了构造函数,a就不用调用析构函数了,但是程序退出时,a变量仍要调用自己的析构函数。
另外参数按照栈的方式进行调用析构函数,即先调用a的析构函数,后调用d的析构函数。
3.构造函数与析构函数,虚函数
构造函数不能用虚函数:
1). 从存储空间角度,虚函数对应一个指向vtable虚函数表的指针,这大家都知道,可是这个指向vtable的指针其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。
2). 从使用角度,虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个成员函数。而构造函数是在 对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。
3). 构造函数不需要是虚函数,也不允许是虚函数,因为创建一个对象时我们总是要明确指定对象的类型,尽管我们可能通过实验室的基类的指针或引用去访问它但析构却不一定,我们往往通过基类的指针来销毁对象。这时候如果析构函数不是虚函数,就不能正确识别对象类型从而不能正确调用析构函数。
4). 从实现上看,vbtl在构造函数调用后才建立,因而构造函数不可能成为虚函数从实际含义上看,在调用构造函数时还不能确定对象的真实类型(因为子类会调父类的构造函数);而且构造函数的作用是提供初始化,在对象生命期只执行一次,不是对象的动态行为,也没有必要成为虚函数。
5). 当一个构造函数被调用时,它做的首要的事情之一是初始化它的VPTR。因此,它只能知道它是“当前”类的,而完全忽视这个对象后面是否还有继承者。当编译器为这个构造函数产生代码时,它是为这个类的构造函数产生代码——既不是为基类,也不是为它的派生类(因为类不知道谁继承它)。所以它使用的VPTR必须是对于这个类的VTABLE。而且,只要它是最后的构造函数调用,那么在这个对象的生命期内,VPTR将保持被初始化为指向这个VTABLE, 但如果接着还有一个更晚派生的构造函数被调用,这个构造函数又将设置VPTR指向它的 VTABLE,等.直到最后的构造函数结束。VPTR的状态是由被最后调用的构造函数确定的。这就是为什么构造函数调用是从基类到更加派生类顺序的另一个理由。但是,当这一系列构造函数调用正发生时,每个构造函数都已经设置VPTR指向它自己的VTABLE。如果函数调用使用虚机制,它将只产生通过它自己的VTABLE的调用,而不是最后的VTABLE(所有构造函数被调用后才会有最后的VTABLE)。
析构函数可以是虚函数:在实现多态时,当用基类操作派生类,在析构时防止只析构基类而不析构派生类的状况发生。
-
#include<iostream>
-
using namespace std;
-
class Base
-
{
-
public:
-
Base(){
-
cout<<"Base 构造函数"<<endl;
-
};
-
virtual ~Base()
-
{
-
cout<<"Base 析构函数"<<endl;
-
};
-
};
-
-
class Drived: public Base
-
{
-
public:
-
Drived()
-
{
-
cout<<"Drived 构造函数"<<endl;
-
};
-
~Drived()
-
{
-
cout<<"Drived 析构函数"<<endl;
-
};
-
};
-
-
int main()
-
{
-
Base* a = new Drived;
-
delete a;
-
return 0;
-
}
函数输出:
Base 构造函数
Drived 构造函数
Drived 析构函数
Base 析构函数
Base的析构函数为虚函数,用基类的指针去操作继承类的成员,释放指针a的过程是:只是释放了继承类的资源,再调用基类的析构函数.
如果不需要基类对派生类及对象进行操作,则不能定义虚函数,因为这样会增加内存开销.当类里面有定义虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间.所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数.
考虑同样的情况:
-
int main()
-
{
-
Drived d;
-
Base a = d;
-
return 0;
-
}
函数输出:
Base 构造函数
Drived 构造函数
Base 析构函数
Drived 析构函数
Base 析构函数
Base a是新的变量。
阅读(942) | 评论(0) | 转发(0) |