C++回顾之拷贝构造函数

        构造函数有很多,有不带参数的系统提供的默认构造函数,可以重载的普通构造函数,对象的拷贝构造函数以及转换构造函数。现在主要回顾对象的拷贝构造函数。构造函数的目的是用于正确地初始化类中的数据成员,拷贝构造函数的作用也在于此,不过初始化的方式不同,拷贝构造函数是使用一个已经存在的对象来初始化一个新的同一类型的对象。即对象成员的复制。

        拷贝构造函数在声明时有要求:必须只有一个参数,并且该参数为该类对象的引用。

        如果类中没有声明拷贝构造函数,则系统将会自动地生成一个缺省的拷贝构造函数,属性为public.

        在以下两种情况会调用拷贝构造函数:

        (1) 当函数的形参是类对象时,这时需要在内存中建立一个局部的对象, 系统要将实参的内容拷贝至形参中,相当于用一个对象来初始化另一个对象,理所当然的要调用拷贝构造函数。

        (2) 当函数的返回值是类对象时,也要建立一个临时的对象,要将函数返回时的局部类对象的内容拷贝至临时对象的内容中,相当于用一个对象来初始化另一个对象,这种情况也要调用拷贝构造函数。但是,为什么这时候系统会创建一个临时的对象呢?为什么不直接返回局部类对象呢?因为局部类对象在离开建立它的函数时就消亡了,不可能在返回调用函数后继续生存。所以编译系统会在调用函数的表达式中创建一个无名的临时对象,该临时对象的生存周期只在函数调用的表达式中。所谓return对象,实际上是调用拷贝构造函数把该对象的值拷入临时对象。如果返回的是变量,处理过程是类似的,只不过不调用构造函数。

        所以,建议将函数的形参与返回值对象声明为对象的引用,而不是对象,这样可以减少内存的使用,不仅节省空间,效率也更高

        前面说过,当类中没有创建任何一个构造函数时,系统就会自动为我们创建一个不带参数的默认构造函数,但是哪怕创建了一个构造函数,系统就不再为我们创建默认的构造函数了,同理,拷贝构造函数也类似,如果我们在类中没有创建一个拷贝构造函数,则系统会为我们自动创建一个默认的拷贝构造函数,如果我们自己创建了拷贝构造函数,系统就不再为我们创建了。

        下面举例说明:

        以下代码是Test类的头文件Test.h的内容:

#ifndef  TEST_H
#define  TEST_H

class Test
{
public:
    Test();        //默认构造函数
    Test(int num); //重载构造函数
    ~Test();       //析构函数
    
    Test(const Test &other); //拷贝构造函数
    Test & operator=(const Test &other);  //=号运算符重载

private:
    int num_;

};

#endif
        下面是Test类的实现Test.cpp:

//默认构造函数
Test::Test():num_(0)
{
    cout << "Initializing default..."<< endl;
}

//构造函数的重载
Test::Test(int num):num_(num)
{
    cout << "Initializing ... "<< num_ << endl;
}

Test::~Test()
{
    cout << "Destroy..."<<endl;
}

//拷贝构造函数的实现
Test::Test(const Test &other):num_(other.num_)
{
    cout << "Initializing with other..."<<endl;
}

Test &Test::operator= (const Test &other )
{
    if( this == &other )
    {
        return *this;
    }

    num_ = other.num_;
    return *this;
}
        

        拷贝构造函数使用的简单例子:

int main()
{
    Test t(10); //调用带一个参数的构造函数
    Test t2(t); //调用拷贝构造函数

    Test t3 = t; //与Test t3(t)等价,这时不是等号赋值运算,在初始化时=号运算表示初始化,不是赋值。

    return 0;
}

        下面这个例子是关于函数的形参是对象而非引用时,以及函数的返回值是对象而非引用时调用拷贝构造函数的情形,还有许多使用拷贝构造函数时需要注意的一些细节,代码解释已经很清楚了,不再做过多的说明。

        

//当形参是对象,而非引用时,要调用拷贝构造函数
void TestFun(const Test t)
{
    
}

//形参为引用,不调用拷贝构造函数
void TestFun2(const Test &t )
{

}

//函数返回为对象,而非引用,调用拷贝构造函数
Test TestFun3(const Test &t )
{
    return t ; //当函数返回时,会创建一个临时对象,用t初始化这个临时对象,临时对象在return后如果没有人接管,它将立刻被销毁。需要注意:此时返回的t与实际返回的Test对象,他们不是同一个对象,实际返回的是临时创建的对象。
}

const Test & TestFun4(const Test & t)
{
    return t;
}


int main()
{
    Test t(10); //调用带一个参数的构造函数
    TestFun(t);//值传递,实参对象初始化形参对象,调用拷贝构造函数。
    TestFun2(t);//引用传递,不分配空间,不再调用拷贝构造函数,减少内存的复制操作。
    
    t = TestFun3(t); //函数在执行时也调用拷贝构造函数,函数返回一个临时对象,最后是对象之间的等号赋值操作,即调用operator=(),赋值成功后,临时对象没人接管,被销毁。

    Test t2 = TestFun3(t); //本质是初始化,把临时对象初始化到t2,相当于把临时对象更名为t2,即临时对象得到接管,所以这时临时对象不会被销毁。

    Test &t3 =  TestFun3(t);//引用要指向临时对象,此时临时对象也得到接管,所以这时临时对象不会被销毁。

    TestFun3(t); //返回的临时对象没人接管,所以立刻被销毁。

    Test t4 = TestFun4(t); //创建t4对象时,TestFun4()返回的是引用对象,这个引用对象要初始化到t4,即用一个对象来初始化另一个对象,本质上还是要调用拷贝构造函数,这种情况与TestFun3()是不一样的,注意区分。

    const Test &t5 = TestFun4(t); //t5引用指向t,不调用拷贝构造函数。

    return 0;    
}



        以上代码最需要注意的就是当函数返回的是对象而非引用时,函数返回的临时对象需要调用拷贝构造函数,这是临时对象如果在声明创建变量或引用时被赋值,如上面的

        Test t2 = TestFun3(t);和 Test &t3 = TestFun3(t),说明临时对象有人接管,这时临时对象不会立刻销毁,否则临时对象将立刻被销毁。

        


猜你喜欢

转载自blog.csdn.net/ab198604/article/details/18964267