目录 Table of Contents
15 对象拷贝-重载赋值运算符
通过复制运算符实现对象拷贝
对象拷贝的两种形式
-
使用拷贝构造函数
-
使用
=
运算符
两种方式各有利弊, 根据实际需要来选择
面向对象中的 =
运算符
- 对象比较简单的时候的赋值操作
#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("编程达人");
}
现在两个字符串地址不一样了, 也就是说一个对象的这个字符串被删之后, 不影响另一个对象的字符串了
-
如果要重载赋值运算符, 必须对所有的属性都要进行处理
所有的成员都要进行处理 -
如果有父类, 要显式调用父类的重载复制运算符
#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("编程达人");
}