C++类对象的赋值与复制(一)
本文主要介绍C++中类对象的赋值操作、复制操作,以及两者之间的区别,另外还会讲到“深拷贝”与“浅拷贝”的相关内容。
本系列内容会分为三篇文章进行讲解。
1 对象的赋值
1.1 what
如同基本类型的赋值语句一样,同一个类的对象之间也是可以进行赋值操作的,即将一个对象的值赋给另一个对象。
对于类对象的赋值,只会对类中的数据成员进行赋值,而不对成员函数赋值。
例如:obj1 和 obj2 是同一类 ClassA 的两个对象,那么对象赋值语句“obj2 = obj1;” 就会把对象 obj1 的数据成员的值逐位赋给对象 obj2。
1.2 代码示例
下面展示一个对象赋值的代码示例(object_assign_and_copy_test1.cpp),如下:
#include <iostream>
using namespace std;
class ClassA
{
public:
// 设置成员变量的值
void SetValue(int i, int j)
{
m_nValue1 = i;
m_nValue2 = j;
}
// 打印成员变量的值
void ShowValue()
{
cout << "m_nValue1 is: " << m_nValue1 << ", m_nValue2 is: " << m_nValue2 << endl;
}
private:
int m_nValue1;
int m_nValue2;
};
int main()
{
// 声明对象obj1和obj2
ClassA obj1;
ClassA obj2;
obj1.SetValue(1, 2);
// 对象赋值场景 —— 将obj1的值赋给obj2
obj2 = obj1;
cout << "obj1 info as followed: " << endl;
obj1.ShowValue();
cout << "obj2 info as followed: " << endl;
obj2.ShowValue();
return 0;
}
编译并运行上述代码,结果如下:
上面的执行结果表明,通过对象赋值语句,我们将obj1的值成功地赋给了obj2。
1.3 几点说明
对于对象赋值,进行以下几点说明:
- 进行对象赋值时,两个对象的必须属于同一个类,如对象所述的类不同,在编译时将会报错;
- 两个对象之间的赋值,只会让这两个对象中数据成员相同,而两个对象仍然是独立的。例如在上面的示例代码中,进行对象赋值后,再调用 obj1.set() 设置 obj1 的值,并不会影响到 obj2 的值;
- 对象赋值是通过赋值运算函数实现的。每一个类都有默认的赋值运算符,我们也可以根据需要,对赋值运算符进行重载。一般来说,需要手动编写析构函数的类,都需要重载赋值运算符(具体原因下文会介绍);
- 在对象声明之后,进行的对象赋值运算,才属于“真正的”对象赋值运算,即使用了赋值运算符“=”;而在对象初始化时,针对对象进行的赋值操作,其实是属于对象的复制。示例如下:
// 声明obj1和obj2 ClassA obj1; ClassA obj2; obj2 = obj1; // 此语句为对象的赋值 // 声明obj1 ClassA obj1; // 声明并初始化obj2 ClassA obj2 = obj1; // 此语句属于对象的复制
1.4 进一步研究
下面从内存分配的角度分析一下对象的赋值操作。
1.4.1 C++中对象的内存分配方式
在C++中,只要声明了对象,对象实例在编译的时候,系统就需要为其分配内存了。一段代码示例如下:
class ClassA
{
public:
ClassA(int id, char* name)
{
m_nId = id;
m_pszName = new char[strlen(name) + 1];
strcpy(m_pszName, name);
}
private:
char* m_pszName;
int m_nId;
};
int main()
{
ClassA obj1(1, "liitdar");
ClassA obj2;
return 0;
}
在上述代码编译之后,系统为 obj1 和 obj2 都分配相应大小的内存空间(只不过对象 obj1 的内存域被初始化了,而 obj2 的内存域的值为随机值)。两者的内存分配效果如下:
1.4.2 默认的赋值运算符
延续上面的示例代码,我们执行“obj2 = obj1;”,即利用默认的赋值运算符将对象 obj1 的值赋给 obj2。使用类中默认的赋值运算符,会将对象中的所有位于 stack 中的域进行相应的复制操作;同时,如果对象有位于 heap 上的域,则不会为目标对象分配 heap 上的空间,而只是让目标对象指向源对象 heap 上的同一个地址。
执行了“obj2 = obj1;”默认的赋值运算后,两个对象的内存分配效果如下:
因此,对于类中默认的赋值运算,如果源对象域内没有 heap 上的空间,其不会产生任何问题。但是,如果源对象域内需要申请 heap 上的空间,那么由于源对象和目标对象都指向 heap 的同一段内容,所以在析构对象的时候,就会连续两次释放 heap 上的那一块内存区域,从而导致程序异常。
~ClassA()
{
delete m_pszName;
}
1.4.3 解决方案
为了解决上面的问题,如果对象会在 heap 上存在内存域,则我们必须重载赋值运算符,从而在进行对象的赋值操作时,使不同对象的成员域指向不同的 heap 地址。
重载赋值运算符的代码如下:
// 赋值运算符重载需要返回对象的引用,否则返回后其值立即消失
ClassA& operator=(ClassA& obj)
{
// 释放heap内存
if (m_pszName != NULL)
{
delete m_pszName;
}
// 赋值stack内存的值
this->m_nId = obj.m_nId;
// 赋值heap内存的值
int nLength = strlen(obj.m_pszName);
m_pszName = new char[nLength + 1];
strcpy(m_pszName, obj.m_pszName);
return *this;
}
使用上面重载后的赋值运算符对对象进行赋值时,两个对象的内存分配效果如下:
这样,在对象 obj1、obj2 退出其的作用域,调用相应的析构函数时,就会释放不同 heap 空间的内存,也就不会出现程序异常了。
关于“对象的复制”的相关内容,请点击此处。
下一篇: 【实战】支持向量机SVM基础实战篇(二)