欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++类对象的赋值与复制(一)

程序员文章站 2024-03-16 10:32:46
...

本文主要介绍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;
}

编译并运行上述代码,结果如下:

C++类对象的赋值与复制(一)

上面的执行结果表明,通过对象赋值语句,我们将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 的内存域的值为随机值)。两者的内存分配效果如下:

C++类对象的赋值与复制(一)

1.4.2 默认的赋值运算符

延续上面的示例代码,我们执行“obj2 = obj1;”,即利用默认的赋值运算符将对象 obj1 的值赋给 obj2。使用类中默认的赋值运算符,会将对象中的所有位于 stack 中的域进行相应的复制操作;同时,如果对象有位于 heap 上的域,则不会为目标对象分配 heap 上的空间,而只是让目标对象指向源对象 heap 上的同一个地址。

执行了“obj2 = obj1;”默认的赋值运算后,两个对象的内存分配效果如下:

C++类对象的赋值与复制(一)

因此,对于类中默认的赋值运算,如果源对象域内没有 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;
    }

使用上面重载后的赋值运算符对对象进行赋值时,两个对象的内存分配效果如下:

C++类对象的赋值与复制(一)

这样,在对象 obj1、obj2 退出其的作用域,调用相应的析构函数时,就会释放不同 heap 空间的内存,也就不会出现程序异常了。

关于“对象的复制”的相关内容,请点击此处