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

时间:2015-02-09 13:26:04

标签: oop object constructor deep-copy

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

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

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

5 个答案:

答案 0 :(得分:4)

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

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

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

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

答案 1 :(得分:2)

如果你只是通过引用复制一个对象,你得到:

Whatever a = new Whatever();

Whatever b = a;

a.myField = "stuff";

你最终还是b.myField还包含" stuff"。这是因为它是完全相同的对象。

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

实施例

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

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

myPrius.regNumber ="AB14 33ND";

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

这是一个人为的例子,你可能在这个例子中使用工厂,但它是一个例子,你几乎相同的地方字段,但你需要保持独立性。

答案 2 :(得分:0)

  

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

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

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

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

答案 3 :(得分:0)

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

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类拷贝构造函数就是, 折叠|复制代码

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

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

有三种一般情况下调用复制构造函数而不是赋值运算符:

  • 实例化一个对象并使用值初始化它 另一个对象(如上例所示)。

  • 传递对象时 值。

  • 按值从函数返回对象时。

在其他情况下,

A a;
a = b; 

调用Assignment运算符

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

答案 4 :(得分:0)

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

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

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

深层复制也是Prototype创作模式的基础。