拷贝构造函数,类的赋值运算符重载,深拷贝与浅拷贝

1,首先明确拷贝构造函数与重载赋值操作符在没有定义的情况下,编译器也会为我们生成一个,这说明这两个函数是一个类必不可少的部分。如果一个类没有定义任何的东西,编译器也会帮助我们生成下面的4个函数:1、一个构造函数,2、析构函数,3、复制构造函数,4、重载赋值操作符。

2,默认的拷贝构造函数 和 重载重载赋值操作符 都是对象间的位拷贝(浅拷贝),也就是把对象里的值完全复制给另一个对象。在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这会导致2个问题:(1)A,B同时使用这块内存时,会修改对方的数据,这不是程序员的本意,而且错误不易发现;(2)当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。

3,基于2中的问题,就需要重写拷贝构造函数 和 重载重载赋值操作符。

3.1 什么时候需要重写

一般,我们我们需要手动编写析构函数的类,都需要overload 拷贝函数和赋值运算符。拷贝构造函数、重载赋值操作符、析构函数这三部分,这三个函数是一致的,如果需要手动定义了其中了一个,那么另外的两个也需要定义,通常在存在指针或者前期相关操作的情况下,都需要手动的定义。

3.2 例子

class A
{
public:

    A()
    {
    }

    A(int id,char *t_name)
    {
        _id=id;
        name=new char[strlen(t_name)+1];
        strcpy(name,t_name);
    }

    A(const A& a)

       {

        if(name!=NULL)
                delete name;
        _id=a._id;
        len=strlen(a.name);
        name=new char[len+1];
        strcpy(name,a.name);

        }
    A& operator =(const A& a)
//注意:此处一定要返回对象的引用,否则返回后其值立即消失!
    {
            if(name!=NULL)
                delete name;
        this->_id=a._id;
        int len=strlen(a.name);
        name=new char[len+1];
        strcpy(name,a.name);
        return *this;
    }


    ~A()
    {
        cout<<"~destructor"<<endl;
        delete name;
    }


    int _id;
    char *name;
}
;

int main()
{
 A a(1,"herengang");
 A b;
 b=a;
}

3.3 解析

note1:
    不要按值向函数传递对象。如果对象有内部指针指向动态分配的堆内存,丝毫不要考虑把对象按值传递给函数,要按引用传递。并记住:若函数不能改变参数对象的状态和目标对象的状态,则要使用const修饰符 

note2:问题:
    对于类的成员需要动态申请堆空间的类的对象,大家都知道,我们都最好要overload其赋值函数和拷贝函数。拷贝构造函数是没有任何返回类型的,这点毋庸置疑。 而赋值函数可以返回多种类型,例如以上讲的void,类本身class1,以及类的引用 class &? 问,这几种赋值函数的返回各有什么异同?
    答:1 如果赋值函数返回的是void ,我们知道,其唯一一点需要注意的是,其不支持链式赋值运算,即a=b=c这样是不允许的!
          2 对于返回的是类对象本身,还是类对象的引用,其有着本质的区别!
              第一:如果其返回的是类对象本身
   A operator =(A& a)
    
{
            if(name!=NULL)
                delete name;
        this->_id=a._id;
        int len=strlen(a.name);
       name=new char[len+1];
        strcpy(name,a.name);
        return *this;
    }

          其过程是这样的:
                       class1 A("herengnag");
                        class1 B;   
                        B=A;
                    看似简单的赋值操作,其所有的过程如下:
                       1 释放对象原来的堆资源
                       2 重新申请堆空间
                       3 拷贝源的值到对象的堆空间的值
                       4 创建临时对象(调用临时对象拷贝构造函数),将临时对象返回
                       
5. 临时对象结束,调用临时对象析构函数,释放临时对象堆内存
my god,还真复杂!!
            但是,在这些步骤里面,如果第4步,我们没有overload 拷贝函数,也就是没有进行深拷贝。那么在进行第5步释放临时对象的heap 空间时,将释放掉的是和目标对象同一块的heap空间。这样当目标对象B作用域结束调用析构函数时,就会产生错误!!
            因此,如果赋值运算符返回的是类对象本身,那么一定要overload 类的拷贝函数(进行深拷贝)!
            第二:如果赋值运算符返回的是对象的引用,
   A& operator =(A& a)
    {
            if(name!=NULL)
                delete name;
        this->_id=a._id;
        int len=strlen(a.name);
       name=new char[len+1];
        strcpy(name,a.name);
        return *this;
    }

        那么其过程如下:
                   1 释放掉原来对象所占有的堆空间
                   1.申请一块新的堆内存
                   2 将源对象的堆内存的值copy给新的堆内存
                   3 返回源对象的引用
                    4 结束。
    因此,如果赋值运算符返回的是对象引用,那么其不会调用类的拷贝构造函数,这是问题的关键所在!!

4 深拷贝与浅拷贝

3中重写的拷贝构造函数就是深拷贝。深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。

你可能感兴趣的:(浅拷贝,深拷贝,拷贝构造函数,类的赋值操作符)