在面向对象编程中复制对象的目的是什么?

问题描述 投票:2回答:5

我已经学习了c#大约3个月了,我今天遇到的一件事是对象的深层和浅层复制构造函数的概念(刚刚习惯了基类的概念,继承和实例化 - 多态仍然没有'真的沉没了但是老实说......我离题了。

当我在基类上看到一个复制深层构造函数(在本例中是一个用户类)时,我首先想到的是“为什么你要制作一个对象的副本?”。我看过的文章解释了如何操作以及它是如何工作的,但我仍然无法找到为什么要这样做的实际例子。如果我有一类人,我当然只是创建一个人类的新实例?

我很欣赏这里可能存在一些基本的东西,但是如果有人可以填补那些很棒的空白。一个真实的例子展示它的实用性会更好!干杯!

oop object constructor deep-copy
5个回答
4
投票

在许多情况下,您可能需要复制对象。

例如,在创建对象时,通常需要以调用方法来设置值的形式初始化它,或者以将参数传递给构造函数的形式。有时,执行所有这些初始化可能相当于很多工作。如果您想要一个仅与另一个对象A相差一个新值的新对象B,则可能更容易获得A的副本作为B并更改单个B值,而不是从头开始创建B.

作为另一个例子,某个逻辑可能要求制作副本。当国际象棋游戏算法想要进行下一步行动时,它可以在内部制作当前电路板的许多副本,用它可能做出的许多可能移动中的一个来修改它们中的每一个,使用一些启发法评估每个新电路板,挑选最好的,然后使用它作为新的当前板。 (甚至可以通过将最好的电路板复制到当前电路板,但更有可能通过设置“当前电路板”参考指向最佳电路板。)

此外,当您更多地了解编程的科学和美术时,您将不可避免地遇到的是防御性副本的概念。当一个Person对象被要求它的DateOfBirth时,它可能不一定返回对它自己的DateOfBirth的引用,因为有人可能会改变这个对象,从而改变Person的DateOfBirth。因此,Person对象可能会返回其DateOfBirth的防御性副本。类似的概念是快照拷贝的概念。如果我有一个事件要传递给事件处理程序列表,我可能想在开始调用处理程序之前获取列表的(浅)快照副本,因为有些处理程序可能决定从列表中删除它们正在处理清单,这可能会带来灾难性的后果。 (ConcurrentModificationException,查一查。)


2
投票

如果您只是通过引用复制对象,您会得到:

Whatever a = new Whatever();

Whatever b = a;

a.myField = "stuff";

你最终得到的b.myField也包含“东西”。这是因为它是完全相同的对象。

但是,如果您要创建一个您想要独立维护的原型对象,则需要以深层副本复制字段。

Car priusPrototype = new Car("Toyota", "myModel"); //etc.

Car myPrius = priusPrototype.clone();
Car neighboursPrius = priusPrototype.clone();

myPrius.regNumber ="AB14 33ND";

邻居普鲁斯没有改变,因为myPrius是一个单独的对象。

这是一个人为的例子,你可能在这个例子中使用了一个工厂,但它是一个例子,说明你拥有几乎相同字段的东西,但你需要保持独立性。


0
投票

如果我有一类人,我当然只是创建一个人类的新实例?

如果您希望Person的第二个实例也具有相同的名称,相同的年龄,相同的一切,该怎么办?

复制对象以便最终得到两个表示相同状态的实例(但彼此独立)的过程称为克隆或复制。

在某些情况下,您可以通过创建新实例并调用所有必需的setter来手动执行此操作,但有时它们并非全部在公共接口中公开。无论哪种方式,它都是副本。


0
投票

复制构造函数是一个特殊的构造函数,它从现有对象初始化一个新对象。编译器将创建一个默认的复制构造函数,如果你不创建它,它会一点一点地复制你的类数据。那么为什么我们必须重新创建一个Copy构造函数?考虑这个课程(不是专业代码)。

Class A
{
    public int *p;
    A(){ p = new int;}
    ~A(){
         delete p;
     }
};
int main()
{
   A a;
   A b = a; //default copy constructor provided by compiler,which exactly copies a.p pointer to to b.p;
   return 0;
};

但是有一个大问题。当main要死时,它将清除所有堆栈变量。所以可能首先调用“a”析构函数并删除“ap”。接下来将调用析构函数,再次尝试删除已经删除的bp(注意ap和bp指针相同的指针)。这将导致运行时错误。为了克服这个问题,我们必须创建一个副本aonstructor和Assignement运算符(在不同的场景中调用)。所以A类拷贝构造函数将是Collapse |复制代码

class A::A(const class A& RefA)
{
   p = new int;    // create a new p 
   *p = *RefA.p; // copy the value
}

所以现在上面例子中的a,b都会有不同的p指针。赋值运算符的功能也是一样的。但是她的目标是不同的。

有三种常见情况,其中调用复制构造函数而不是赋值运算符:

  • 实例化一个对象并使用另一个对象的值初始化它(如上例所示)。
  • 按值传递对象时。
  • 按值从函数返回对象时。

在其他情况下,

A a;
a = b; 

调用Assignment运算符

答案归功于వేంకటనారాయణ(venkatmakam)


0
投票

首先,您需要清楚浅层复制和深层复制之间的区别。

浅拷贝是对原始实例的唯一引用的副本。所以基本上,在处理副本时,您仍在使用相同的原始实例,在不同的引用之间共享。在某些情况下,当您需要共享单个对象时,或者当您有一个复杂的对象很难并且“性能损失”到深层复制时,这可能很有用。但是在某些情况下共享实例可能是危险的,并且通常需要额外的工作来设计线程安全的类。

而深拷贝则生成一个新实例。所以你有多个具有相同状态的对象(而不是对同一个对象的不同引用),但是它们是独立存在的。因此,在这种情况下,更改复制实例的状态不会反映原始对象的状态。

深刻的副本也是Prototype创作模式的基础。

© www.soinside.com 2019 - 2024. All rights reserved.