构造函数

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了构造函数相关的知识,希望对你有一定的参考价值。

1 构造函数

    1)名字和类名相同。

    2)不能定义返回类型,参数个数可以有任意个。

    3)如果未定义构造函数,系统会自动产生一个默认构造函数。但只要程序中有构造函数的定义,系统就不会再自动产生默认构函。

2 转换构造函数

    只有一个参数的构造函数称为转换构造函数。转换构造函数可以将其他类型转换成类类型。类的构造函数只有一个参数是比较危险的,因为编译器可能隐式的把参数类型转换成类类型。为了避免隐式转换,可以在构造函数前加上explicit,这样编译器就不会隐式调用了。

    假如有一个Test类,他有一个转换构造函数:

    Test(int num) :num_(num)
    {
        cout << "Test(int num)" <<num<< endl;
    }
    ~Test()
    {
        cout << "~Test()" << num_<<endl;
    }

    执行代码:

int main()
{
    Test t(10);
    //t = 20;
    return 0;
}

    执行结果:

技术分享

   这个程序中,使用了转换构造函数“构造”的功能,并未用到其“转换”功能。将代码更改如下:

int main()
{
    Test t(10);
    t = 20;
    return 0;
}

    执行结果:

技术分享

    这里首先调用构造函数对t完成初始化。接着将20赋给t显然类型不符,本应该出错,可是程序中定义了转换构造函数,编译器会隐式的把20作为参数构造出来一个临时的Test对象(如果构造函数前加了explicit关键字,就不能隐式的调用,从而这里出错)。将其赋值给t后,该临时对象需要被析构,因此调用了一次析构函数。最后栈上的t被析构。需要注意的是将临时对象赋给t时,还调用了赋值运算符函数;如果在定义的时候就用一个同类型的对象对其进行初始化,则调用后面将要介绍的拷贝构造函数。

3 构造函数初始化列表

    可以使用初始化列表来对类成员初始化,通过 : 调出初始化列表。放在初始化列表才是真正的初始化,属于初始化段。在函数体中进行初始化实际上是赋值,属于普通计算段。因此,由于const成员和引用要求定义时必须初始化,所以二者的初始化必须放在初始化列表。

    初始化列表还有一个作用:

class Object
{
public:
    Object(int num):num_(num)
    {
        cout<<"Object..."<<num<<endl;
    }
    ~Object()
    {
        cout<<"~Object..."<<endl;
    }
private:
    int num_;
};

class Container
{
public:
    Container()
    {
        cout<<"Container..."<<endl;
    }
    ~Container()
    {
        cout<<"~Container..."<<endl;
    }
private:
    Object obj_;
};

上述代码在实例化Container类对象时会报错。这是因为,Container类对象的成员是Object类对象。因此,在初始化时,需先调用Object的默认构造函数,但Object中没有默认构造函数,而且由于Object中定义了一个带参数的构造函数,所以编译器也不会自动生成一个默认构造函数。要解决这个问题,可以在初始化obj_的时候,调用Object中定义的那个构造函数,而这个调用就在Container的初始化列表中实现:

Container():obj_(0)
{
cout<<"Container..."<<endl;
}

Container(num):obj_(num)
{
cout<<"Container..."<<endl;
}

4 拷贝构造函数

    拷贝构造函数的参数必须是对该类类型的引用,它在两种情况用到:1)当用一个类对象去初始化另一个同类类对象时。2)函数的参数是类对象或者返回值是类对象时。

    (1)如果函数的参数是类对象,在函数结束时,还会调用析构函数将其析构掉;(2)如果参数是类对象的引用,则不会调用拷贝构造函数,也不会调用析构函数。(3)当函数的返回值是类对象时,首先会调用拷贝构造函数。但是,如果用它去初始化一个刚刚定义的类对象,则不会再次调用拷贝构造函数,这是因为相当于给返回的临时对象更了名,例如:Test t=TestFunc();如果没人接收,只是TestFunc(),那么它会立即调用析构函数。(感觉从返回类对象,对该对象接收或不接收,到最终的销毁,都是经历了一次拷贝构造和一次析构)。

 

    当用一个对象去给另一个对象赋值时,会调用赋值运算符函数;当用一个对象去初始化另一个对象时,会调用拷贝构造函数。一定要区分赋值和初始化,才能知道需要调用哪个函数。类最好定义自己的拷贝构造函数与赋值运算符函数,否则可能会导致浅拷贝(当类包含动态成员时)。浅拷贝会引起内存被析构两次等严重问题。

以上是关于构造函数的主要内容,如果未能解决你的问题,请参考以下文章

Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段

防止 Proguard 删除片段的空构造函数

无法解析片段中的 ViewModelProvider 构造?

为啥要避免片段中的非默认构造函数?

片段真的需要一个空的构造函数吗?

这个嵌套类构造函数片段可以应用于泛型类吗?