类的四个默认成员函数->拷贝构造函数

2.拷贝构造函数

1.拷贝构造函数的概念

  拷贝构造函数是一种特殊的构造函数,负责类对象之间的复制,与构造函数相同,当我们没有实现拷贝构造函数时,编译器会为我们产生默认拷贝构造函数,举个栗子:  

class A
{
public:
    A(int _a,int _b) : a(_a),b(_b){
        std::cout << "A constructors" << std::endl;
    }
    A(const A& rhs)
    {
        this->a = rhs.a;
        this->b = rhs.b;
        std::cout << "A copy constructors" << std::endl;
    }
    int a;
    int b;
};
int main()
{
    A a(13,14);
    std::cout << a.a << "," << a.b << std::endl;
    A b(a);
    std::cout << b.a << "," << b.b << std::endl;
    return 0;
}

测试结果为:

A constructors

13,14

A copy constructors

13,14

可以看到,b直接利用a的值为自己完成了对象的创建和赋值。

2.拷贝构造函数的调用时机

在C++中,在三种情况下会发生拷贝构造的调用

1.对象以值传递的方式传入函数参数

   假设存在函数fun(Class_A a){},当调用函数fun(A a)时,一个Class_A类型的对象被传入 。我们知道,函数形参传入,需要生成临时对象.假设传入一个Class_A的对象tset,当test传入时,产生临时对象,调用拷贝构造函数将test的值传给临时对象,执行完fun()之后,析构掉临时对象。上代码:

class A
{
public:
    A(){}
    A(const A&rhs)
    {
        this->a = a;
        this->b = b;
        std::cout << "A copy constructors" << std::endl;
    }
    ~A()
    {
        std::cout << "A destructors" << std::endl;
    }
public:
    int a;
    int b;
};
void fun(A _a)
{

}
int main()
{
    A c;
    fun(c);
    return 0;
}

测试结果是:

A copy constructors;

A destructors;

A destructors;

2.对象以值传递的方式返回

  与上一条类似,如果一个函数的返回值是一个类类型,并且以值传递的方式返回。那么会返回一个临时变量,而这个临时变量会通过拷贝构造函数接受函数的返回值。  

class A
{
public:
    A(){}
    A(const A&rhs)
    {
        this->a = a;
        this->b = b;
        std::cout << "A copy constructors" << std::endl;
    }
public:
    int a;
    int b;
};
A fun()
{
    A a;
    return a;
}
int main()
{
    fun();
    return 0;
}

测试结果如下:

A copy constructors

3.对象显式的通过另外一个对象进行初始化

class A
{
public:
    A(){}
    A(const A&rhs)
    {
        this->a = a;
        this->b = b;
        std::cout << "A copy constructors" << std::endl;
    }
public:
    int a;
    int b;
};
int main()
{
    A a;
    A b(a);
    A c = a;
    return 0;
}

测试结果:

A contructors

A copy contructors

A copy contructors

3.浅拷贝和深拷贝

  拷贝构造有时候会出现一些问题,设想这样一种情况,一个类中有一个成员函数为指针类型,举个栗子:  

class A
{
public:
    A(){
        p = new int(100);
    }
    A(const A&rhs)
    {
        this->p = p;
    }
    ~A()
    {
        delete p;
    }
public:
    int *p;
};
int main()
{
    A a;
    A b(a);
    return 0;
}

测试结果:程序崩溃。

  为什么?我们的拷贝构造函数简单粗暴的拷贝了指针的值,使得a的p指针和b的指针p同时指向堆的同一块空间,当a析构之后,那块空间已经释放,而b再执行析构函数,程序自然崩溃。如果我们不写拷贝构造函数,编译器实现的默认拷贝构造一般都是这种类型的。这种情况我们称之为浅拷贝。

  浅拷贝会导致两种情况:

  1.两个不同的对象会处理操作同一块内存空间,造成互相影响

  2.一块内存空间可能被释放两次!!!造成内存泄漏。

  出现这种情况时,我们需要编写符合实际情况的拷贝构造函数,称之为深拷贝。

  如果数据成员出现指针变量,不是简单的将指针变量赋值,而是重新分配空间。我们改动上面的栗子。

class A
{
public:
    A(int _k = 40){
        k = _k;
        p = new int(100);
    }
    A(const A&rhs)
    {
        this->k = rhs.k;
        this->p = new int;
        *p = *(rhs.p);
    }
    ~A()
    {
        delete p;
    }
public:
    int k;
    int *p;
};
int main()
{
    A a;
    A b(a);
    return 0;
}

测试结果:程序正常执行。

4.拷贝构造函数的几个注意事项

1.拷贝构造函数的参数

  拷贝构造函数的参数是常引用。

  为什么是常量?这样就可以接受常量,也可以接受非常量(拷贝构造函数肯定不会改变传入参数的值)。当然,你也可以去掉const.只是这样一来,无法使用常量来完成拷贝构造而已。

  为什么是引用?引用是必须的,之前提到过,当函数参数以值传递时,会调用拷贝构造函数,如果拷贝构造函数自身居然不是引用,而是以值传递,那么会出现拷贝构造函数的无限递归。不断的调用自身!!!!所以,拷贝构造函数参数必须是引用。

2.拷贝构造函数可以重载吗?

  拷贝构造函数是可以重载的!

  参数可以是常引用,也可以只是单纯的引用。 

    A(const A&rhs)
    {
        this->k = rhs.k;
        this->p = new int;
        *p = *(rhs.p);
    }
    A(A&rhs)
    {
        this->k = rhs.k;
        this->p = new int;
        *p = *(rhs.p);
    }

测试结果:程序正常执行

3.拷贝构造函数可以被继承吗?

  不能,派生类的拷贝构造函数会主动调用基类的拷贝构造。

posted @ 2017-03-17 18:39  是召不是昭  阅读(440)  评论(0编辑  收藏  举报