15_对象拷贝-重载赋值运算符

15 对象拷贝-重载赋值运算符

通过复制运算符实现对象拷贝

对象拷贝的两种形式

  1. 使用拷贝构造函数

  2. 使用 = 运算符

两种方式各有利弊, 根据实际需要来选择

面向对象中的 = 运算符

  • 对象比较简单的时候的赋值操作
#include <stdio.h>
#include <windows.h>

class CBase
{
    private:
        int x;
        int y;
    public:
        CBase(){}
        CBase(int x, int y)
        {
            this->x = x;
            this->y = y;
        }
};

int main(int argc, char* argv[])
{
    CBase c1(1, 2), c2(3, 4);
    c1 = c2;

    return 0;
}

编译发现可以通过, 那么说明 C++ 编译器允许我们把两个对象用 = 赋值来进行运算

我们看看 = 做了哪些事情

执行完毕的时候, 我们发现第二个对象的两个成员的值都赋值给了第一个对象

我们看看反汇编

; c1 = c2;
mov     eax, dword ptr [ebp - 10h]
mov     dword ptr [ebp - 8], eax
mov     ecx, dword ptr [ebp - 0Ch]
mov     dword ptr [ebp - 4], ecx

所以编译器可以替我们处理这种赋值的情况

  • 有父类的时候的赋值操作
#include <stdio.h>
#include <windows.h>

class CBase
{
    private:
        int x;
        int y;
    public:
        CBase(){}
        CBase(int x, int y)
        {
            this->x = x;
            this->y = y;
        }
};

class CSub:public CBase
{
    private:
        int z;
    public:
        CSub(){}
        CSub(int x, int y, int z):CBase(x, y)
        {
            this->z = z;
        }
};

int main(int argc, char* argv[])
{
    CSub c1(1, 2, 3), c2(3, 4, 5);
    c1 = c2;

    return 0;
}

赋值之后, 子类的值发生了变化, 父类的值也发生了变化, 说明赋值运算符也可以处理父类的情况

赋值运算符的问题

实际上赋值运算符和拷贝构造函数都有一个共同的问题, 就是它默认都是浅拷贝

我们看一下上一节的问题代码

class CObject
{
    private:
        int m_nLength;
        char* m_strBuffer;
    public:
        CObject()
        {

        }
        CObject(const char* str)
        {
            m_nLength = strlen(str) + 1;        // 得到传进来字符串的长度
            m_strBuffer = new char[m_nLength];  // 根据你需要的长度在堆中申请了一段空间, 相当于 malloc
            memset(m_strBuffer, 0, m_nLength);  // 对申请的内存进行初始化
            strcpy(m_strBuffer, str);           // 拷贝字符串, 把传进来的字符串拷贝到我申请的这块内存里面去
        }
        ~CObject()                              // 当我们的对象不再需要的时候, 我们可以通过析构函数把这块空间释放掉
        {
            delete[] m_strBuffer;
        }
}

// 我在主函数里面创建的对象, 当我主函数执行完的时候, 这个析构函数会把空间释放掉
int main(int argc, char* argv[])
{
    CObject c1("china"), c2("编程达人");
    c1 = c2;

    return 0;
}

我们现在使用赋值运算符对这两个对象进行操作, 发现两个对象的字符串地址都是同一个地址, 如果你删掉一个对象的话, 另一个对象字符串指向的地址就没了

我们怎么解决这个问题呢 ? 只有一个方法就是重载赋值运算符, 我们自己实现深拷贝.

重载赋值运算符

我们看一下实现好了的代码

#include <stdio.h>
#include <windows.h>

class CBase
{
    private:
        int m_nLength;
        char* m_pBuffer;
    public:
        CBase()
        {
            m_nLength = 0;
            m_pBuffer = NULL;
        }
        CBase(char* szBuffer)
        {
            this->m_nLength = strlen(szBuffer) + 1;
            m_pBuffer = new char[m_nlength];
            strcpy(m_pBuffer, szBuffer);
        }
        CBase& operator = (const CBase& ref)    // 重载赋值运算符
        {
            m_nLength = ref.m_nLength;  // 先把拷贝的那个对象的值复制过来
            if(m_pBuffer != NULL)       // 复制过来以后, 把原来的那块内存释放掉
                delete[] m_pBuffer;
            m_pBuffer = new char[m_nLength];    // 根据新的长度, 重新申请一块新的内存
            memcpy(m_pBuffer, ref.m_pBuffer, m_nLength);    // 然后把要拷贝的对象的那个值整个复制过来
            return *this;       // 返回当前对象的内容
        }
        virtual ~CBase()
        {
            delete[] m_pBuffer;
        }
};

int main(int argc, char* argv[])
{
    CBase c1("china"), c2("编程达人");
}

现在两个字符串地址不一样了, 也就是说一个对象的这个字符串被删之后, 不影响另一个对象的字符串了

  1. 如果要重载赋值运算符, 必须对所有的属性都要进行处理
    所有的成员都要进行处理

  2. 如果有父类, 要显式调用父类的重载复制运算符

#include <stdio.h>
#include <windows.h>

class CBase
{
    private:
        int m_nLength;
        char* m_pBuffer;
    public:
        CBase()
        {
            m_nLength = 0;
            m_pBuffer = NULL;
        }
        CBase(char* szBuffer)
        {
            this->m_nLength = strlen(szBuffer) + 1;
            m_pBuffer = new char[m_nlength];
            strcpy(m_pBuffer, szBuffer);
        }
        CBase& operator = (const CBase& ref)    // 重载赋值运算符
        {
            m_nLength = ref.m_nLength;  // 先把拷贝的那个对象的值复制过来
            if(m_pBuffer != NULL)       // 复制过来以后, 把原来的那块内存释放掉
                delete[] m_pBuffer;
            m_pBuffer = new char[m_nLength];    // 根据新的长度, 重新申请一块新的内存
            memcpy(m_pBuffer, ref.m_pBuffer, m_nLength);    // 然后把要拷贝的对象的那个值整个复制过来
            return *this;       // 返回当前对象的内容
        }
        virtual ~CBase()
        {
            delete[] m_pBuffer;
        }
};

class CSub:public CBase
{
    private:
        int m_nIndex;
    public:
        CSub(){}
        CSub(int nIndex, char* szBuffer):CBase(szBuffer)
        {
            m_nIndex = nIndex;
        }
        CSub& operator = (const CSub& ref)  // 不能直接使用父类写好的重载运算符, 父类的并不能很好地覆盖子类的成员, 所以我们必须要重写
        {
            // 当你在子类里重载赋值运算符的时候, 如果你希望父类的赋值运算符先执行的话, 应该在子类里面显式地调用父类的赋值运算符
            CBase::operator = (ref);

            // 为什么在拷贝构造函数的时候我们不能把它写到函数里面呢 ?
            // 因为子类可以全盘继承父类的任何东西, 但是构造函数和析构函数除外, 所以不能在函数体中显式地调用父类的拷贝构造函数

            m_nIndex = ref.m_nIndex;
            return *this;
        }
};

int main(int argc, char* argv[])
{
    CSub c1("china"), c2("编程达人");
}