绕过不变性


13

我是面向对象编程的新手,一个需要我花一段时间才能掌握的概念是不变性。我认为昨晚灯泡熄灭了,但我想验证一下:

当我遇到无法更改不可变对象的声明时,我会感到困惑,因为例如,我可以执行以下操作:

NSString *myName = @"Bob";
myName = @"Mike";

在那里,我刚刚更改了不可变类型NSString的myName。我的问题是,“对象”一词可以指代内存中的物理对象,也可以指代“ myName”。前一个定义适用于不变性的概念。

对于变量,(对我而言)对不变性的更清楚的定义是,不变对象的只能通过更改其在内存中的位置(即其引用(也称为指针))来更改。

这是正确的,还是我仍然迷失在树林中?


13
您的类型不是NSString,而是“ 和的指针NSString ”,它不是一成不变的。我什么都不知道目标的C,但我在你的例子猜测@"Mike"是创造的一个新实例NSString,并将其分配给指针myName。因此,您没有更改myName所指向的对象,仅更改了它所指向的对象。
fwgx

2
@fwgx回答一下,您会得到我的认可。
Gulshan

谢谢大家的所有答复,它们非常有帮助。我现在了解不可变对象是内存中的值,而不是指向它的变量。
Michael Mangold

@Gulshan Done,请参见下文
。...– fwgx

为了理解不变性,我建议学习一种编程语言,该语言应清楚地将绑定(即为对象命名)与可变性(即,将名称重新分配给另一个对象)分开。ML(任何形式的SML,Ocaml,F#)就是一个很好的例子。
吉尔(Gilles)“所以,别再邪恶了”,

Answers:


4

听起来您正在朝着正确的方向前进,但还没有完全正确。这是错误的:

在那里,我刚刚更改了不可变类型NSString的myName。我的问题是,“对象”一词可以指代内存中的物理对象,也可以指代“ myName”。

在您的代码段中,myName它不是不可变的类型NSString,而是可变的类型NSString*(指向NSString的指针)。听起来您缺少的关键是要了解指针只是另一个值,并且指针与其指向的对象(或如果您在其生命周期中对其进行部分更改的对象)具有完全独立的寿命

你说:

...不可变对象的值只能通过更改其在内存中的位置(即其引用(也称为其指针))来更改。

错了 一个对象不拥有指向该对象的指针,也不受该对象的任何指针的控制或影响。

因此,NSString示例(@"Bob"@"Mike")中的两个对象与myName变量完全分开。它们也彼此完全分开。当您更改myName为指向@"Mike"而不是指向时@"Bob",您不会更改NSString对象。


为了完整起见,我将注意到垃圾收集器使此操作更加复杂,因为对指针的更改可能会影响它们指向的对象。但是,这是一个实现细节,不应影响代码的可观察行为。


17

你不知所措。不变性是指:只要您不更改变量,它将始终“包含”相同的值,而与其他变量无关。

C语言中的反例(经过简化,假设使用允许的架构):

 char *a = "Hello World";
 char *b = a;
 b[0] = 'Y';

现在不再有“包含”(即指向)字符串“ Hello World”,而是“ Yello World”。

在字符串是不可变的语言中,例如Java和(编辑:安全)C#,您不能这样做。没门。这意味着程序的每个部分都可以安全地保留对字符串的引用,并依赖于其内容永不更改。否则,为了安全起见,他们将不得不创建一个副本。

但是变量仍然是可变的。您可以让它指向另一个对象。只是对象本身不会在您的背后改变。


1
技术上,您可以修改在C#中的字符串的内容,你只需要使用unsafe代码。
亚罗诺(Aaronaught)2011年

1
亚伦诺特:感谢您提供的信息,您说得对。对于每个需要知道如何的人:msdn.microsoft.com/zh-cn/library/ms228599.aspx
user281377 2011年

10

您正在将变量与对象混淆。变量可用于存储对对象的引用,但它们不是对象。它是不可变的对象,而不是变量,因此可以将变量从一个对象更改为另一个,但是如果对象是不可变的,则不能更改其属性。

将该物体视为一个大声的,醉酒的邻居。如果他是合理的(可变的),您也许可以敲开他的门,将他转变为他不会发出太大声的生活方式。但是,如果他是一成不变的,那么您唯一的改变就是希望其他人进入!


3
我有一些我想静音的邻居。
戴夫·奈

我认为您在第二段中不需要您的示例,因为您的解释足够好。国际海事组织的例子可能会混淆。但是,+ 1是对小伙子问题的简洁回答。
Paul McCabe

@DaveNay:我对隐形双关语表示歉意。根本不是故意的。
Kilian Foth 2011年

6

变量不是对象。变量是一个名称,它引用一个对象(或更普遍地讲是一个值)。

例如,“守门员”是一个名称,我们用来指代负责捍卫目标的对象(人)。但是,如果我用另一个人代替这个人(因为前者受伤或其他原因),新人现在称为“守门员”。

赋值语句使变量可变(某些语言,例如Haskell没有它,实际上使用不可变变量)。它使您可以重新定义名称的含义,从而重新分配值。

现在对象本身可以是不可变的。几千年前,人们可能认为钻石是一成不变的。无论您对钻石进行了什么操作,都无法对其进行修改。无论您称它为waggawooga(松散地翻译为“我们部落最大的有光泽的石头”)还是不再这样称呼(因为您发现了更大的石头),钻石都保持不变。相比之下,您以前用waggawooga雕刻有趣图片的木头并没有变。它被证明是可变的。即使它始终具有相同的名称。

变量和值都可以是不可变的(独立地)。在这种情况下,对象是不可变的。构建后NSString,您将无法对其进行修改。您可以命名它并传递它,但是它将保持不变。与此相反,NSMutableString可以在创建后进行更改,例如通过调用setString方法。


喜欢waggawooga比较!
Michael K

只是为了避免不必要地混淆原始张贴者:尽管您认为变量不是对象是好的,但变量实际上并未定义为“引用值的名称”。变量可能会被命名,并指向包含值的存储位置。在许多编程语言中,不需要命名变量,在许多语言中,同一变量可以具有多个名称。
埃里克·利珀特

4

我认为您会迷失方向,因为您混用了两个概念:对象本身和绑定到该对象的变量名称。

不可变的对象不能更改。期。但是,绑定到不可变对象的变量名(符号)可以修改为绑定到另一个不可变对象。

换句话说,您在这两行中所做的是:

  1. 创建值为“ Bob”的不可变字符串对象
  2. 将符号绑定myName到该对象
  3. 创建值为“ Mike”的不可变字符串对象
  4. 将符号绑定myName到该对象

2

你对你的类型不是NSString,它是一个“ 指针NSString”,这是不是一成不变的。我对目标C一无所知,但是我猜在您的示例中@"Mike"正在创建的新实例NSString并将其分配给指针 myName。所以,你没有改变该对象myName指向只是,什么它指向。


0

这是一个可变对象的示例:charC中的数组:

char str[10];

我可以改变内容str(只要它不超过10个字符),以我的心脏的内容。为了使它可以被C字符串库函数识别为字符串,必须有一个结尾的0,因此它最多可以容纳9个字符的字符串:

strcpy(str, "hello"); // str now contains the string "hello\0"
strcpy(str, "world"); // str now contains the string "world\0"

str[0] = 'W';         // str now contains "World\0"

如此美好

将此与Java中的字符串对象进行对比:

String foo = "Hello";

字符串实例(包含字符“ H”,“ e”,“ l”,“ l”和“ o”的内存块)不能被修改;我无法更改该字符串的任何内容。当你写类似

foo = foo + " World";

您没有在“ Hello”实例的末尾附加“ World”;您正在创建一个新实例,将“ Hello World”复制foo到该实例,并进行更新以引用该新字符串实例。

foo本身不包含字符串实例;它仅引用该实例(类似于C或Obj-C中的指针),因此为什么将诸如String之类的类型称为引用类型。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.