析构函数、构造函数用来进行数据的销毁和初始化。那么系统默认生成的构造和析构有什么特点呢?
构造函数
对于自定义类型,会调用对应的默认构造,内置类型不做处理。
当显式定义了无参的默认构造,初始化列表和函数体都为空时,也会在初始化列表阶段调用自定义类的默认构造(所有成员变量都要经历初始化列表)。
在C++中,如果你定义了一个类A,它包含自定义类型B和C的成员,当你创建A的实例并使用A()这样的默认构造函数时,编译器会隐式调用B和C的默认构造函数(如果它们存在的话)来初始化A中的B和C成员。
class B {
public:
B() { // 默认构造函数
// 初始化B...
}
};
class C {
public:
C() { // 默认构造函数
// 初始化C...
}
};
class A {
public:
B b; // B类型的成员
C c; // C类型的成员
A() { // A的默认构造函数
// 构造函数体为空,但初始化列表会调用B和C的默认构造函数
}
};
在这个例子中,当你执行 A a; 时,会发生以下步骤:
-
分配内存以容纳A类型的对象。
-
调用B的默认构造函数来初始化成员b。
-
调用C的默认构造函数来初始化成员c。
-
执行A的默认构造函数体(在这个例子中,它是空的)。
先走初始化列表,所有成员都会经历初始化列表阶段,然后再走函数体。
在C++中,对于内置类型(如int, float, double等)的成员变量,如果在类的构造函数中没有显式地初始化它们,那么这些内置类型的成员将不会被初始化。它们的值将是未定义的,这意味着它们可能包含内存中的任何垃圾值。
析构函数
默认生成的析构函数根据成员变量,按照它们在类中声明的相反顺序被调用。即,最后声明的成员变量首先被销毁。
在如下的代码中,析构函数被显式定义。
class CustomType {
public:
~CustomType() {
// 自定义析构函数,负责释放资源
}
};
class MyClass {
public:
CustomType customMember; // 自定义类型的成员
// 默认生成的析构函数
~MyClass() {
// customMember的析构函数会被自动调用
}
};
即使内部显示实现了MyClass
的析构函数,即使该析构函数的函数体为空,编译器也会自动为MyClass
的成员customMember
调用其析构函数。在C++中,类的析构函数负责释放对象拥有的资源,对于类类型的成员,编译器会在类的析构函数中自动插入代码来调用这些成员的析构函数。
解释:
当MyClass
的对象超出作用域或者通过delete
操作符被销毁时,MyClass
的析构函数会被调用。在这个析构函数中,编译器生成的代码会自动调用customMember
的析构函数,无论MyClass
的析构函数体是否为空。
如果在析构函数的函数体进行一些其他操作,还会自动调用CustomType的析构吗?
答案是会的!
即使在析构函数的函数体中进行了其他操作,编译器仍然会自动调用CustomType
的析构函数。C++编译器会在执行析构函数的函数体中的代码之前或之后(具体顺序取决于成员变量的声明顺序),插入对成员变量析构函数的调用。
class MyClass {
public:
CustomType customMember; // 自定义类型的成员
// 显示实现的析构函数
~MyClass() {
// 这里可以执行其他操作
// ...
// 编译器会在函数体的末尾(或者成员声明顺序的逆序)自动调用customMember的析构函数
}
};
析构函数的调用顺序遵循以下规则:
- 执行析构函数体内的代码。
- 按照成员变量在类中声明的逆序,自动调用每个成员变量的析构函数。
因此,即使在析构函数中执行了其他操作,编译器也会保证成员变量的析构函数被正确调用,从而确保了资源被正确释放。这是C++语言的一个特性,以确保对象析构时的资源管理。
对于内置类型(如int, float, double等):
-
不调用析构函数:内置类型不需要析构函数,因为它们不涉及资源管理。默认生成的析构函数不会对内置类型的成员变量执行任何特殊操作。
-
值销毁:当内置类型的成员变量所在的类实例被销毁时,这些成员变量的值会被简单地销毁。这意味着它们的存储空间会被释放,但不会有特殊的析构逻辑。
此外,系统默认生成的赋值重载与拷贝构造,都会进行浅拷贝。