C++编程语言中类对象的赋值与复制介绍(二)

本系列文章主要介绍C++编程语言中类对象的赋值操作和复制操作,以及两者之间的区别,另外还会介绍“深拷贝”与“浅拷贝”的相关知识。

本文为系列文章的第二篇,主要介绍C++编程语言中类对象的复制的相关知识。

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,执行完这条语句后,临时对象也自动消失了。

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

  • 8
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liitdar

赠人玫瑰,手有余香,君与吾共勉

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值