初始化列表,以及构造函数和析构函数的调用顺序

新学到了C++里新的初始化方法,初始化列表。因此来整理一下。若有错漏,还望指摘。

构造函数
构造函数可分为普通构造函数和拷贝构造函数。

  1. 普通构造函数

    构造函数就是在类实例化时,自动调用来生成类的函数。若未定义,则系统自动生成。比如:

    class test{};
    test A;
    

    在生成对象A的时候就会调用普通构造函数。
    形式为函数名与类名相同,没有也不能声明任何类型的返回值。

  2. 拷贝构造函数

    拷贝构造函数是在对类进行拷贝时,自动调用来根据已经实例化的对象来实例化一个新的对象的函数。若未定义则系统自动生成。
    比如:

    class test{};
    test A, B(A), C=A;
    

    在B和C构造的时候都会调用拷贝构造函数。
    此时值得注意的是,像C这样在最开始的声明中使用了赋值运算符进行拷贝的情况,虽然使用了赋值符号,但实际上调用的是拷贝构造函数,而不是重载的赋值运算符,而如下面的这种情况才会调用重载的赋值运算符:

    test A,C;
    C = A;
    

    (实际上也很好理解,第一种情况其实是在构造对象,第二种情况是构造好了之后的赋值,而赋值运算符并不是构造函数,所以在构造的时候虽然写的是赋值符号,但实际调用的是拷贝构造函数。)
    拷贝函数的形式是,函数名是类名,参数必须只有一个,是该类的引用或者指针,一般为该类的const引用,同样没有也不能声明任何类型的返回值。

初始化列表
构造过程其实分为初始化阶段与计算阶段。
对于类,可以用初始化列表来实现构造函数在初始化阶段直接赋初值。

class A{
public:
    A(int _x, int _y) : x(_x), y(_y) {}		//带初始化列表的普通构造函数
    A(const A& a) : x(a.getX()), y(a.getY()) {}		//拷贝构造函数
    int getX() const { return x; }
    int getY() const { return y; }
private:
    const int x, y;
};

初始化列表以冒号开始,形式为 变量名(初值),多个变量之间以逗号分隔。
初始化列表的用处在于,它可以对const修饰的常量进行初始化。而若写在大括号中,是在计算阶段赋值,而常量是不能在计算阶段进行赋值的。
初始化列表只能用于构造函数中,无论是普通构造函数还是拷贝构造函数。
成员变量中含有常量时,只能用初始化列表进行初始化。

注:最开始,我不知道此处可以直接读取引用a的private数据。因此以为必须用到封装函数来读取。而读取时又会报错,最后发现,必须将封装函数也设置为const才可以。而实际上可以直接 A(const A& a) : x(a.x), y(a.y) {} 这样实现。
后来我才明白,封装是针对类而言,而不是对象。因此定义在该类中的函数,哪怕不是构造函数,只要传入的参数是本类,就可以访问这个参数的私有成员变量。另外,友类也可以访问。

析构函数
析构函数是在销毁对象时,系统会自动调用的函数。如果未定义,系统会自动生成。
形式为波浪线~加上类名,同样没有也不能声明任何类型的返回值。比如:

classs test{
    ~test(){}
};

调用顺序:
如下的结构:

class A{};
class B{};
Class C : public A {
public:
    B a;
    B b;
};
int main(){
    A *p = new C;
    delete p;
    p = nullptr;

那么main函数中在构造C的时候,先调用基类构造函数A(),然后是成员变量a的构造函数B(),然后是b的构造函数B(),最后是自身的构造函数C()。
但是在析构的时候,是根据指针来析构。如上面这个情况,因为p是A类指针,所以其实是只调用A的析构函数。若将p改为C类指针,则析构函数的调用顺序与构造函数相反,先是C本身的析构函数,然后是成员变量b的析构函数,然后是a,最后是基类的析构函数。若想析构顺序与指针无关,需要将析构函数定义为虚函数。

注:构造函数和析构函数只有用C++的new和delete才会被调用,如果用malloc和free,不会自动调用这两个函数

原创文章 34 获赞 41 访问量 5972

猜你喜欢

转载自blog.csdn.net/qq_44844115/article/details/88953624
今日推荐