析构函数
通过C++ Primer重新回顾构造函数和析构函数,发现真的好多都忘了…
构造函数
类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。
构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。
对于构造函数有以下几点需要注意:
- 构造函数与类同名,并且没有返回值。
- 构造函数和其他成员函数一样,都能够被重载
- 构造函数不能够被声明成const,当我们创建一个const对象,直到构造函数初始化完成,对象才能获得真正的常量属性。因此构造函数可以创建对象过程中对其写值。
默认构造函数
C++ 默认构造函数是对类中的参数提供默认值的构造函数,一般情况下,是一个没有参数值的空函数。当没有显示声明构造函数的时候,编译器会隐式生成一个默认构造函数,并且这个构造函数什么都不会做。如果在类中显示申明构造函数,编译器将不会隐式生成默认构造函数。默认构造函数初始化规则如下:
- 如果类中有初始值,用它来初始化成员。
否则默认初始化该成员。
#include<iOStream> using namespace std; class Point { public: //Point(); ~Point(); void print(); private: int x; int y; }; //Point::Point() // { //} Point::~Point() { cout<<"Destructor is called"<<endl; } void Point::Print() { cout<<"x = "<<x<<" y = "<<y<<endl; } int main(int argc, char const *argv[]) { Point b; b.Print(); return 0; }
最后,会发现成员变量x,y都没有被初始化。和现实声明默认成员函数,但是什么操作都不做是一样的。当显示声明构造函数之后,编译器将不会隐式生成默认构造函数。
默认构造函数不适用场景
在下面几种情况中,默认构造函数是不适用的。
1.编译器只有在发现类没有声明任何构造函数才会生成一个默认构造函数。例如如下声明:
class Point
{
Point(int x,int y);
~Point();
};
Point::Point(int x,int y)
{
this->x=x;
this->y=y;
}
Point a;
此时会出现编译错误。
2.对于类中包含内置类型或者复合类型(数组或者指针)成员,只有当这些成员全部被赋予了类内的初始值,这个类才适合使用默认构造函数。
暂时还没有搞懂。。
3.编译器不能为某些类合成默认构造函数。例如类中包含其他类类型成员,而这个成员没有默认构造函数。
class A
{
public:
A(int x)
{
this->x=x;
}
~A(){};
private:
int x;
};
class B
{
A a;
};
在B的成员中,需要调用A的默认构造函数,但是由于A中没有默认构造函数。
构造函数初始化列表
构造函数初始化列表是以一个冒号开始,然后用逗号分隔数据列表。形如:
class MyClass
{
public:
MyClass(int x,int y,int z):a(x),b(y),c(z){}
~MyClass(){}
private:
int a;
int b;
int c;
};
当某个数据成员被构造函数初始化列表忽略,会被默认构造函数隐式初始化。
构造函数不能够轻易覆盖类内的初始值,除非新赋值与原值不同。如果不能使用类内初值,应该显示初始化每个内置类型成员。
必须使用构造函数初始化列表的情况
如果成员是const,引用、或者属于某种未提供构造函数的类类型,我们必须通过过早函数初始化列表对其进行初始化。
例如:
class ConstRef
{
public:
ConstRef(int ii);
~ConstRef();
private:
int i;
const int ci;
int &ri;
};
此时必须将ci和ri初始化,如果采用下述方法:
ConstRef::ConstRef(int ii)
{
i=ii;
ci=ii; //错误:不能给const赋值
ri=i; //错误:ri没有被初始化
}
正确的做法是采用初始化列表
ConstRef::ConstRef(int ii):i(ii),ci(ii),ri(i){}
成员初始化顺序
成员的初始化顺序与他们在类定义中出现的顺序一致。例如:
class M
{
public:
M(int x);
~M(){}
private:
int a;
int b;
int c;
};
M::M(int x):c(x),a(x),b(x)
{
}
此时的初始化顺序实际上是a,b,c。而不是c,a,b。
因此最好令构造函数成初始值顺序与成员的声明顺序保持一致,如果可能,尽量避免使用成员初始化其他成员。
析构函数
析构函数是一类特殊的成员函数,它会在每次删除所建行对象执行。
析构函数具有如下特点:
- 析构函数也和类名相同,不过需要在前面加上~便于与构造函数区分。
- 析构函数没有返回值,并且不能够被重载,这一点和构造函数不同
- 当对象被撤销时析构函数自动调用,与构造函数不同的是,析构函数可以被显示调用,已释放对象中动态申请的内存。
- 无法声明为 const、volatile 或 static。 但是,可以为声明为 const、volatile 或 static 的对象的析构调用它们。
析构的顺序
当对象超出范围或被删除时,其完整析构中的事件序列如下所示:
1.将调用该类的析构函数,并且会执行该析构函数的主体。
2.按照非静态成员对象的析构函数在类声明中的显示顺序的相反顺序调用这些函数。 用于这些成员的构造的可选成员优化列表不影响构造或析构的顺序。 (有关初始化成员的详细信息,请参阅初始化基和成员。)
3.非虚拟基类的析构函数以声明的相反顺序被调用。
4.虚拟基类的析构函数以声明的相反顺序被调用。
通过下面一段代码可以看出,构造函数和析构函数的执行顺序是相反的,当然,里面涉及到了函数的继承问题。
#include<iostream>
using namespace std;
class A
{
public:
A()
{
cout<<"constructing A"<<endl;
}
~A()
{
cout<<"destructing A"<<endl;
}
private:
int a;
};
class B: public A
{
public:
B()
{
cout<<"constructing B"<<endl;
}
~B()
{
cout<<"destructing B"<<endl;
}
private:
int b;
};
class C: public B
{
public:
C()
{
cout<<"constructing C"<<endl;
}
~C()
{
cout<<"destructing C"<<endl;
}
private:
int c;
};
int main()
{
C c;
return 0;
}
提一个常见误区:struct和class的区别不是struct只能包含数据成员,而是权限的问题,class默认访问权限是private,而struct默认访问权限是public。
析构函数涉及到的东西比较多,留到下次总结。
参考资料
- C++ Primer 第五版
- C++ Primer Plus
- 析构函数 (C++)