我已经学习了c#大约3个月了,我今天遇到的一件事是对象的深层和浅层复制构造函数的概念(刚刚习惯了基类的概念,继承和实例化 - 多态仍然没有'真的沉没了但是老实说......我离题了。
当我在基类上看到一个复制深层构造函数(在本例中是一个用户类)时,我首先想到的是“为什么你要制作一个对象的副本?”。我看过的文章解释了如何操作以及它是如何工作的,但我仍然无法找到为什么要这样做的实际例子。如果我有一类人,我当然只是创建一个人类的新实例?
我很欣赏这里可能存在一些基本的东西,但是如果有人可以填补那些很棒的空白。一个真实的例子展示它的实用性会更好!干杯!
在许多情况下,您可能需要复制对象。
例如,在创建对象时,通常需要以调用方法来设置值的形式初始化它,或者以将参数传递给构造函数的形式。有时,执行所有这些初始化可能相当于很多工作。如果您想要一个仅与另一个对象A相差一个新值的新对象B,则可能更容易获得A的副本作为B并更改单个B值,而不是从头开始创建B.
作为另一个例子,某个逻辑可能要求制作副本。当国际象棋游戏算法想要进行下一步行动时,它可以在内部制作当前电路板的许多副本,用它可能做出的许多可能移动中的一个来修改它们中的每一个,使用一些启发法评估每个新电路板,挑选最好的,然后使用它作为新的当前板。 (甚至可以通过将最好的电路板复制到当前电路板,但更有可能通过设置“当前电路板”参考指向最佳电路板。)
此外,当您更多地了解编程的科学和美术时,您将不可避免地遇到的是防御性副本的概念。当一个Person
对象被要求它的DateOfBirth
时,它可能不一定返回对它自己的DateOfBirth
的引用,因为有人可能会改变这个对象,从而改变Person的DateOfBirth
。因此,Person
对象可能会返回其DateOfBirth
的防御性副本。类似的概念是快照拷贝的概念。如果我有一个事件要传递给事件处理程序列表,我可能想在开始调用处理程序之前获取列表的(浅)快照副本,因为有些处理程序可能决定从列表中删除它们正在处理清单,这可能会带来灾难性的后果。 (ConcurrentModificationException
,查一查。)
如果您只是通过引用复制对象,您会得到:
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是一个单独的对象。
这是一个人为的例子,你可能在这个例子中使用了一个工厂,但它是一个例子,说明你拥有几乎相同字段的东西,但你需要保持独立性。
如果我有一类人,我当然只是创建一个人类的新实例?
如果您希望Person的第二个实例也具有相同的名称,相同的年龄,相同的一切,该怎么办?
复制对象以便最终得到两个表示相同状态的实例(但彼此独立)的过程称为克隆或复制。
在某些情况下,您可以通过创建新实例并调用所有必需的setter来手动执行此操作,但有时它们并非全部在公共接口中公开。无论哪种方式,它都是副本。
复制构造函数是一个特殊的构造函数,它从现有对象初始化一个新对象。编译器将创建一个默认的复制构造函数,如果你不创建它,它会一点一点地复制你的类数据。那么为什么我们必须重新创建一个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)
首先,您需要清楚浅层复制和深层复制之间的区别。
浅拷贝是对原始实例的唯一引用的副本。所以基本上,在处理副本时,您仍在使用相同的原始实例,在不同的引用之间共享。在某些情况下,当您需要共享单个对象时,或者当您有一个复杂的对象很难并且“性能损失”到深层复制时,这可能很有用。但是在某些情况下共享实例可能是危险的,并且通常需要额外的工作来设计线程安全的类。
而深拷贝则生成一个新实例。所以你有多个具有相同状态的对象(而不是对同一个对象的不同引用),但是它们是独立存在的。因此,在这种情况下,更改复制实例的状态不会反映原始对象的状态。
深刻的副本也是Prototype创作模式的基础。