对象的复制和赋值有什么区别?各自的应用场景是怎么样的?

探讨C++中对象赋值与拷贝构造的区别,包括赋值运算符与拷贝构造函数的调用时机,以及它们如何影响对象的生命周期和内存布局。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

部分引用:https://blog.youkuaiyun.com/liitdar/article/details/81874541

对象赋值

如同基本类型的赋值语句一样,同一个类的对象之间也是可以进行赋值操作的,即将一个对象的值赋给另一个对象。

对于类对象的赋值,只会对类中的数据成员进行赋值,而不对成员函数赋值。

给对象赋值和调用拷贝构造函数初始化另一个对象行为是类似的,但是有所区别,下面会讲到(实际上调用的是拷贝构造函数)

拷贝构造函数和赋值

拷贝构造函数和赋值运算符的行为比较相似,都是将一个对象的值复制给另一个对象;但是其结果却有些不同,拷贝构造函数使用传入对象的值生成一个新的对象的实例,而***赋值运算符是将对象的值复制给一个已经存在的实例。***这种区别从两者的名字也可以很轻易的分辨出来,拷贝构造函数也是一种构造函数,那么它的功能就是创建一个新的对象实例;赋值运算符是执行某种运算,将一个对象的值复制给另一个对象(已经存在的)。
调用的是拷贝构造函数还是赋值运算符,主要是看是否有新的对象实例产生。如果产生了新的对象实例,那调用的就是拷贝构造函数;如果没有,那就是对已有的对象赋值,调用的是赋值运算符。
以下代码用于辨析何时使用了拷贝构造、何时使用了赋值运算符

复制

使用拷贝构造函数操作对象的方式,称为“对象的复制”。

int main()
{
    Person p;
    Person p1 = p;    // 1
    Person p2;
    p2 = p;           // 2
    f(p2);            // 3

    p2 = f1();        // 4

    Person p3 = f1(); // 5

    getchar();
    return 0;
}

1、使用对象 p创建了一个新的对象p1,毫无疑问使用的是拷贝构造函数
2、p2这个对象已经提前声明好了,执行的仅仅只是将p的值复制给p2
3、这个就是拷贝构造函数 调用时机 之一“对象以值传递的方式传入函数参数”
4、这条语句拷贝构造函数和赋值运算符都调用了。函数f1以值的方式返回一个Person对象,在返回时会调用拷贝构造函数创建一个临时对象tmp作为返回值;返回后调用赋值运算符将临时对象tmp赋值给p2
5、直接调用拷贝构造函数使用f1()返回值创建了对象p3

特别要说的是

  1. 对象进行赋值时,两个对象必须属于同一个类;
  2. s2 = s1; 只会使得学生2和学生1的各项数据相同,但是学生还是两个独立的个体;
  3. 对象赋值是通过赋值运算函数实现的\
  4. 在对象声明之后,进行的对象赋值运算,才属于“真正的”对象赋值运算,即使用了赋值运算符“=”;而在对象初始化时,针对对象进行的赋值操作,其实是属于对象的复制。
//就是说  通过一个对象创建了另一个对象 调用的是拷贝构造函数(strcpy函数),执行的是复制操作
	Person p;
    Person p1 = p;    // 1
    
//对象全部创建好之后,执行的"="  才算是赋值操作
    ClassA obj1;
    ClassA obj2;
    obj2 = obj1; // 此语句为对象的赋值

赋值操作中内存变化

int main()
{
    ClassA obj1(1, "liitdar");
    ClassA obj2;
 
    return 0;
}

上述中,声明了对象obj1和obj2之后,系统将会为其分配相应大小的内存空间(由于obj1初始化了,所以空间中存放的是liitdar 但是obj2并没初始化,所以存的是随机值)
注:图片来源https://blog.youkuaiyun.com/liitdar/article/details/81874541

执行赋值操作后 obj2 = obj2; 会将对象中的所有位于 stack 中的域进行相应的复制操作;

目前就到这个程度,望指正。

### lambda表达式中赋值变量的数据类型 在C++中,`lambda`表达式是一种匿名函数对象,其底层实现依赖于编译器生成的唯一类名[^1]。当定义一个`lambda`表达式时,它实际上会创建一个新的匿名类实例,并且该类实现了调用操作符(`operator()`)。这意味着每个`lambda`表达式的类型实际上是独一无二的。 对于`lambda`表达式中的局部变量捕获参数传递,存在一些特定规则: #### 局部变量捕获 - 如果`lambda`表达式需要访问外部作用域中的变量,则这些变量必须满足“实质上是`final`”的要求,即它们在整个生命周期内不会发生更改[^5]。 - 这些被捕获的变量会被复制到`lambda`表达式的闭包环境中,或者通过引用的方式绑定到原始变量上(取决于捕获模式 `[=]` 或 `[&]`)。 #### 参数列表与隐式推导 - `lambda`表达式的参数类型可以显式指定或让编译器自动推断。例如,在不提供具体类型的场景下,编译器能够依据上下文来决定实际使用的数据类型[^2]。 以下是几个例子展示如何处理不同类型以及捕获机制: ```cpp #include <iostream> using namespace std; int main() { int x = 42; // 显式声明参数类型 auto explicitLambda = [](const int param) -> void { cout << param * 2 << endl; }; explicitLambda(x); // 隐式推导参数类型 auto implicitLambda = [&](auto value) { cout << value + x << endl; }; implicitLambda(7); return 0; } ``` 上述代码片段展示了两种不同风格的`lambda`定义方式及其应用情况。第一个`explicitLambda`强制指定了输入参数为整数;第二个则允许任意兼容加法运算的对象传入,并利用了对外围变量`x`的引用捕捉功能。 需要注意的是,尽管两者的逻辑看起来相似,但由于各自所属类别差异巨大——一个是独立实体,另一个关联至原生范围内的某个固定数值——所以即便两者行为一致也无法互相转换或比较。 最后强调一点关于Java平台上的区别对待:虽然同样支持`lambda`语法糖衣包裹下的简化版匿名内部类构建过程[^3][^4] ,但因应宿主语言特性的缘故,在那里讨论的主题更多集中在其接口适配能力而非细粒度控制层面的东西。 ### 结论 综上所述,`lambda`表达式所涉及的各种元素均遵循严格的规定以保障程序健壮性的同时也赋予开发者极大的灵活性去完成复杂任务需求。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值