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

版权声明:本文为博主原创文章,商业转载请联系作者获得授权,非商业转载请注明出处。 https://blog.csdn.net/liitdar/article/details/81877373

本文承接前文,讲述“对象的复制”的相关内容。

1 对象的复制

1.1 what

相对于“对已声明的对象使用赋值运算符进行的对象赋值”操作,使用拷贝构造函数操作对象的方式,称为“对象的复制”。

类的拷贝构造函数是一种特殊的构造函数,其形参是本类对象的引用。拷贝构造函数的作用为:在创建一个新对象时,使用一个已经存在的对象去初始化这个新对象。例如语句“ClassA obj2(obj1);”就使用了拷贝构造函数,该语句在创建新对象 obj2 时,利用已经存在的对象 obj1 去初始化对象 obj2。

对象的赋值与对象的复制,貌似都是只对类的成员变量进行拷贝,而不会对类的成员函数进行操作。—— 待进一步确认。

1.2 拷贝构造函数的特点

拷贝构造函数有以下特点:

  • 拷贝构造函数也是一种构造函数,所以其函数名与类名相同,并且该函数也没有返回值类型;
  • 拷贝构造函数只有一个参数,并且该参数是其所属类对象的引用;
  • 每一个类都必须有一个拷贝构造函数,我们可以根据需要重载默认的拷贝构造函数(自定义拷贝构造函数),如果没有重载默认的拷贝构造函数,系统就会生成产生一个默认的拷贝构造函数,默认的拷贝构造函数将会复制出一个数据成员完全相同的新对象;

1.3 自定义拷贝构造函数

这里展示一个自定义拷贝构造函数的代码示例(object_assign_and_copy_test2.cpp),如下:

#include <iostream>

using namespace std;

class ClassA
{
public:
    // 普通构造函数
    ClassA(int i, int j)
    {
        m_nValue1 = i;
        m_nValue2 = j;
    }
    // 自定义的拷贝构造函数
    ClassA(const ClassA& obj)
    {
        m_nValue1 = obj.m_nValue1 * 2;
        m_nValue2 = obj.m_nValue2 * 2;
    }
    // 打印成员变量的值
    void ShowValue()
    {
        cout << "m_nValue1 is: " << m_nValue1 << ", m_nValue2 is: " << m_nValue2 << endl;
    }
private:
    int m_nValue1;
    int m_nValue2;
};


int main()
{
    // 创建并初始化对象obj1,此处调用了普通构造函数
    ClassA obj1(1, 2);
    // 创建并初始化对象obj2,此处调用了自定义的拷贝构造函数
    ClassA obj2(obj1);

    obj1.ShowValue();
    obj2.ShowValue();
    
    return 0;
}

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

上述执行结果表明,通过调用自定义的拷贝构造函数,我们在创建对象 obj2 时,结合对象 obj1 的成员变量的值,完成了我们自定义的初始化过程。

1.4 调用形式上的区别

我们可以从调用形式上,对“对象的赋值”和“对象的复制”进行区分。在此,我们列出一些对应关系:

  • 对象的赋值:指的是调用了类的赋值运算符,进行的对象的拷贝操作;
  • 对象的复制:指的是调用了类的拷贝构造函数,进行的对象的拷贝操作。

上面的对应关系是不严谨的,因为有些情况下,即使使用了赋值运算符“=”,但其实最终使用的仍然是类的拷贝构造函数,这就引出了拷贝构造函数的两种调用形式。

拷贝构造函数的调用语法分为两种:

  • 类名 对象2(对象1)。例如:“ClassA obj2(obj1);”,这种调用拷贝构造函数的方法称为“代入法”;
  • 类名 对象2 = 对象1。例如:“ClassA obj2 = obj1;”,这种调用拷贝构造函数的方法称为“赋值法”。

拷贝构造函数的“赋值法”就很容易与“对象的赋值”场景混淆,其二者之间的区别是:对象的赋值场景必须是建立在源对象与目标对象均已声明的基础上;而拷贝构造函数函数的赋值法,必须是针对新创建对象的场景。代码如下:

【对象的赋值】:

    // 声明对象obj1和obj2
    ClassA obj1;
    ClassA obj2;

    obj1.SetValue(1, 2);
    // 对象赋值场景 —— 将obj1的值赋给obj2
    obj2 = obj1;

【拷贝构造函数的“赋值法”】:

    // 创建并初始化对象obj1,此处调用了普通构造函数
    ClassA obj1(1, 2);
    // 创建并初始化对象obj2,此处调用了自定义的拷贝构造函数
    ClassA obj2 = obj1;

当然,为了代码的清晰化,建议使用拷贝构造函数的“代入法”,更可以让人一眼就看出调用的是拷贝构造函数。

1.5 调用拷贝构造函数的三个场景

1.5.1 类对象初始化

当使用类的一个对象去初始化另一个对象时,会调用拷贝构造函数(包括“代入法”和“赋值法”)。示例代码如下:

    // 创建并初始化对象obj1,此处调用了普通构造函数
    ClassA obj1(1, 2);
    // 创建并初始化对象obj2,此处调用了自定义的拷贝构造函数
    ClassA obj2 = obj1;    // 代入法
    ClassA obj3 = obj1;    // 赋值法

1.5.2 类对象作为函数参数

当类对象作为函数形参时,在调用函数进行形参和实参转换时,会调用拷贝构造函数。示例代码如下:

// 形参是类ClassA的对象obj
void funA(ClassA obj)
{
    obj.ShowValue();
}
    
int main()
{
    ClassA obj1(1, 2);

    // 调用函数funA时,实参obj1是类ClassA的对象
    // 这里会调用拷贝构造函数,使用实参obj1初始化形参对象obj
    funA(obj1);
    
    return 0; 
}

说明:在上面的main函数内,语句“funA(obj1);”就会调用拷贝构造函数。

1.5.3 类对象作为函数返回值

当函数的返回值是类的对象、在函数调用完毕将返回值(对象)带回函数调用处,此时会调用拷贝构造函数,将函数返回的对象赋值给一个临时对象,并传到函数的调用处。示例代码如下:

// 函数funB()的返回值类型是ClassA类类型
ClassA funB()
{
    ClassA obj1(1, 2);
    // 函数的返回值是ClassA类的对象
    return obj1;
}

int main()
{
    // 定义类ClassA的对象obj2
    ClassA obj2;
    // funB()函数执行完成、返回调用处时,会调用拷贝构造函数
    // 使用obj1初始化obj2
    obj2 = funB();
    
    return 0;
}

说明:在上面的main函数内,语句“obj2 = funB();”就会调用拷贝构造函数。由于对象obj1是函数funB中定义的,在函数funB结束时,obj1的生命周期就结束了,因此在函数funB结束之前,执行语句"return obj1"时,会调用拷贝构造函数将obj1的值拷贝到一个
临时对象中,这个临时对象是系统在主程序中临时创建的。funB函数结束时,对象obj1消失,但是临时对象将会通过语句“obj2 = funB()”赋值给对象obj2,执行完这条语句后,临时对象也自动消失了。

关于“深拷贝”与“浅拷贝”,以及赋值运算符的重载、拷贝构造函数的重载的相关内容,请点击此处

猜你喜欢

转载自blog.csdn.net/liitdar/article/details/81877373
今日推荐