为什么可以使用另一种方法而不是赋值运算符来更改函数中 Ruby 中局部变量的值?
Why can you change the value of a local variable in Ruby in a function using another method but not with an assignment operator?
我试图将 Ruby 的概念理解为一种传递引用值的语言。使用我在 this 网站上找到的示例...
def uppercase(value)
value.upcase!
end
name = 'William'
uppercase(name)
puts name
我们得到输出“WILLIAM”。所以 value 和 name 都指向同一个对象,该对象最初持有“William”的值,然后 .upcase 将其更改为“WILLIAM”。我会将其理解为传递引用。
但是如果我将 .upcase
更改为 =
:
def uppercase2(value)
value = "WILLIAM"
end
name = 'William'
uppercase2(name)
puts name
输出变成“William”。这是按值传递。
为什么赋值运算符会影响变量在 Ruby 中的处理方式?
这里的关键是您永远无法更改 Ruby 对象的核心 self
,一旦它被创建,它将始终是同一个对象,直到被垃圾收集和销毁。只能更改对象的 属性 。
但是,您可以更改 变量 或对象引用,例如实例变量、常量、attr_accessor
属性等等。
所以在这种情况下:
def uppercase2(value)
value = "WILLIAM"
end
这会重新分配 value
局部变量。它对原始 object.
没有任何作用
如果要替换该文本,则需要在对象上使用方法来实现它(如果支持)。在这种情况下有一个方法:
def uppercase2(value)
value.replace("WILLIAM")
end
这些通常被称为 in-place 修改 因为对象本身是被操纵的而不是交换为另一个对象。
让我们开始用更容易理解的方式来分解它。
您可以将 name
(或 value
)等变量与路标进行比较。
假设我将它指向米色房子,这将是变量的值。
考虑到以上内容,让我们看一下第一个示例:
# don't worry about this line for now
House = Struct.new(:color)
def color_red(value)
value.color = :red
end
house = House.new(:beige)
color_red(house)
puts house
# prints: #<struct House color=:red>
那么这里发生了什么?当我们将 house
作为参数传递给 color_red
时,Ruby 将 复制 我们的路标并将其分配给 value
。现在两个路标都指向同一所房子。然后我们按照路标value
的指示走到房子,把它漆成红色。
因此 house
的颜色最终会变成红色。
现在我们来看另一个例子:
def color_red(value)
value = House.new(:red)
end
house = House.new(:beige)
color_red(house)
puts house
# prints: #<struct House color=:beige>
我们从这里开始,我们复制我们的路标house
并将副本分配给value
。然而,我们不会走到房子前去粉刷它,而是要修改路标并将其指向街道下方某处的一所红房子。因为我们的路标 value
是 house
的副本,将其指向新方向不会影响 house
。
您的代码正在做同样的事情。当您调用 value.upcase!
时,您是在对她说字符串。嘿,你,把你所有的角色都大写起来! (类似于粉刷房子。)
当您重新分配 value
(value = "WILLIAM"
) 时,您实际上只是在修改路标并将其指向新的方向。但是,路标作为副本传递,因此不会影响原始路标。
def uppercase(value)
value.upcase!
end
name = 'William'
uppercase(name)
puts name #WILLIAM
在这种情况下,您正在改变原始对象。 name
指向 William
value
也是如此。当你传入参数时,Ruby 将赋值
参数变量 value
指向 name
指向的同一个对象。
def uppercase2(value)
value = "WILLIAM"
end
name = 'William'
uppercase2(name)
puts name
在这种情况下,您正在重新分配 value
。也就是说,你正在改变哪个
对象 value
指向。它指向相同的字符串对象
名字指向。但是,现在,您要求 value
引用一个
不同的对象。
因此,总而言之,upcase!
会改变对象,而 =
会 re-assign。
你可以想到 3 个圆圈,value
在一个圈子里,name
在另一个圈子里,William
在第三。
value
和name
都指向字符串对象,William
.
在第一种情况下,你改变了 value
和 name
的字符串对象
指向。
在第二种情况下,您正在创建第 4 个圆圈,其中包含 WILLIAM
。然后你擦除从 value
到 William
的线并创建一条线
从 value
到 WILLIAM
.
如果我这样做你就明白了:
def uppercase2(value)
value = "WILLIAM"
puts value
end
name = 'William'
uppercase2(name) # => “WILLIAM”
puts name # William
I'm trying to understand the concept of Ruby as a pass-by-reference-value language.
Ruby 是 pass-by-value。总是。它永远不会 pass-by-reference。传递的值是一个不可变的不可伪造的指针,但非常不同于pass-by-reference。
C 是 pass-by-value。 C有指针。这并不意味着如果你在 C 中传递一个指针,它会神奇地变成 pass-by-reference。还是pass-by-value.
C++有指针,和它同时支持pass-by-value和pass-by-reference。在 C++ 中,您可以按值或按引用传递指针,也可以按值或按引用传递 non-pointers。这两个概念是完全正交的。
如果我们可以同意C是pass-by-value,并且我们可以同意C有指针,并且我们可以同意当我们在C中传递一个指针时,它仍然是pass-by-value,那么我们还必须同意 Ruby 是 pass-by-value,因为 Ruby 的行为类似于 C 的假设版本,其中唯一允许的类型是“指向某物的指针”,这是访问值的唯一方法正在取消引用指针,传递值的唯一方法是获取指针。
这不会以任何方式改变在 C 中传递的参数,这意味着它仍然是 pass-by-value,这意味着如果我们在 C pass-by-value 中调用它,调用它没有意义Ruby.
中的任何其他内容
def uppercase(value)
value.upcase!
end
name = 'William'
uppercase(name)
puts name
we get the output "WILLIAM". So value and name are both pointing at the same object, which is initially holding a value of "William" and then .upcase changes it to "WILLIAM". I would understand this as pass-by-reference.
同样,这是 不是 pass-by-reference。 Pass-by-reference 意味着您可以 更改调用方范围内的引用 ,而 Ruby 不允许您这样做。
这只不过是简单的突变。 Ruby 不是纯函数式语言,它确实允许您改变对象。当你改变一个对象时,无论你给它起什么名字,你都可以观察到它改变的状态。
我的朋友叫我“Jörg”,但我的理发师叫我“Mittag 先生”。当我的理发师给我剪头发时,当我遇到我的朋友时它不会神奇地长回来,它仍然会消失,即使他们提到我的名字和我的理发师不一样。
你对同一个对象有两个名字,你改变了那个对象。无论您使用哪个名称来引用该对象,您都将观察到新状态。
那只是“可变状态”,与pass-by-reference无关。
But then if I change .upcase
to =
:
def uppercase2(value)
value = "WILLIAM"
end
name = 'William'
uppercase2(name)
puts name
The output becomes "William". Which is pass by value.
Why does an assignment operator make the difference in how a variable is treated in Ruby?
没有。两种情况都是pass-by-value。在第二种情况下,您创建了一个新对象并将其分配给 uppercase2
方法内的局部变量 value
。 (从技术上讲,它不是一个局部变量,它是一个参数绑定,但它可以在方法体内被反弹,正是因为Ruby是pass-by-value。如果它是 pass-by-reference,那么这将 也 将 name
局部变量重新分配给新创建的对象。)
有时,pass-by-value 的这种 特定 情况称为 [=,其中传递的值是指向潜在可变对象的不可变指针118=]、call-by-sharing 或 call-by-object。但那是 而不是 不同于 pass-by-value 的东西。还是还是pass-by-value。它是 pass-by-value 的特例,其中值不能是“任何值”,而始终是“不可变的不可伪造的指针”。
有时,您会听到这样的描述“Ruby 是 pass-by-value,其中传递的值是一个引用”或“Ruby 是 pass-by-reference-值”或“Ruby 是 pass-by-value-参考”或“Ruby 是 pass-by-object-reference”。我不太喜欢这些术语,因为它们听起来非常接近“pass-by-reference”,但实际上“pass-by-reference”中的术语“参考”和“pass-by-object-reference”中的术语“参考”表示两个不同的事物。
在“pass-by-reference”中,术语“引用”是一个技术术语,可以认为是对“变量”、“存储位置”等概念的概括。在“pass-by-value-reference”,我们说的是“对象引用”,它更像是指针,但不能被制造或更改。
我也不喜欢上面使用的术语“pass-by-value,其中传递的值是一个不可变的不可伪造的指针”,因为术语“指针”具有一定的含义,尤其是对于来自C、在C中,你可以做指针运算,你可以把一个数字转换成一个指针,即你可以“凭空变出一个指针”。您可以在 Ruby 中完成 none。这就是为什么我添加了形容词“不可变”(没有算术)和“不可伪造”(你不能创建一个指针,只能由系统传递一个),但人们忽视或忽视它们或低估它们的重要性。
在某些语言中,这些指向对象的不可变不可伪造指针被称为“对象引用”(这又是危险的,因为它会引起与“pass-by-reference”的混淆)或 OOPS(Object-Oriented PointerS)具有大多数不受限制的free-for-all“C”指针的不幸含义。 (例如,Go 有更多的限制性指针,但是 whn你只是说“指针”这个词,没有人会想到围棋。)
请注意,其中 none 确实特定于 Ruby。 Python、ECMAScript、Java 和许多其他语言的行为方式相同。 C# 的默认行为方式相同,但也支持 pass-by-reference 作为 显式 opt-in。 (您 必须 在方法定义和方法调用时显式请求 pass-by-reference。)Scala 默认情况下的行为方式相同,但可以选择支持 call-by-name。
C# 实际上是展示这些区别的一种很好的方式,因为 C# 同时支持 pass-by-value 和 pass-by-reference,值类型和引用类型,显然,您可以将类型都编写为可变类型和不可变类型,所以你实际上得到了所有 8 种可能的不同组合,你可以研究它们的行为方式。
我想出了这个简单的测试代码,您也可以轻松地将其翻译成其他语言:
def is_ruby_pass_by_value?(foo)
foo.replace('More precisely, it is call-by-object-sharing!')
foo = 'No, Ruby is pass-by-reference.'
end
bar = 'Yes, of course, Ruby *is* pass-by-value!'
is_ruby_pass_by_value?(bar)
p bar
# 'More precisely, it is call-by-object-sharing!'
Here is the slightly more involved example in C#:
struct MutableCell { public string value; }
static void ArgumentPassing(string[] foo, MutableCell bar, ref string baz, ref MutableCell qux)
{
foo[0] = "More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.";
foo = new string[] { "C# is not pass-by-reference." };
bar.value = "For value types, it is *not* call-by-sharing.";
bar = new MutableCell { value = "And also not pass-by-reference." };
baz = "It also supports pass-by-reference if explicitly requested.";
qux = new MutableCell { value = "Pass-by-reference is supported for value types as well." };
}
var quux = new string[] { "Yes, of course, C# *is* pass-by-value!" };
var corge = new MutableCell { value = "For value types it is pure pass-by-value." };
var grault = "This string will vanish because of pass-by-reference.";
var garply = new MutableCell { value = "This string will vanish because of pass-by-reference." };
ArgumentPassing(quux, corge, ref grault, ref garply);
Console.WriteLine(quux[0]);
// More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.
Console.WriteLine(corge.value);
// For value types it is pure pass-by-value.
Console.WriteLine(grault);
// It also supports pass-by-reference if explicitly requested.
Console.WriteLine(garply.value);
// Pass-by-reference is supported for value types as well.
我试图将 Ruby 的概念理解为一种传递引用值的语言。使用我在 this 网站上找到的示例...
def uppercase(value)
value.upcase!
end
name = 'William'
uppercase(name)
puts name
我们得到输出“WILLIAM”。所以 value 和 name 都指向同一个对象,该对象最初持有“William”的值,然后 .upcase 将其更改为“WILLIAM”。我会将其理解为传递引用。
但是如果我将 .upcase
更改为 =
:
def uppercase2(value)
value = "WILLIAM"
end
name = 'William'
uppercase2(name)
puts name
输出变成“William”。这是按值传递。
为什么赋值运算符会影响变量在 Ruby 中的处理方式?
这里的关键是您永远无法更改 Ruby 对象的核心 self
,一旦它被创建,它将始终是同一个对象,直到被垃圾收集和销毁。只能更改对象的 属性 。
但是,您可以更改 变量 或对象引用,例如实例变量、常量、attr_accessor
属性等等。
所以在这种情况下:
def uppercase2(value)
value = "WILLIAM"
end
这会重新分配 value
局部变量。它对原始 object.
如果要替换该文本,则需要在对象上使用方法来实现它(如果支持)。在这种情况下有一个方法:
def uppercase2(value)
value.replace("WILLIAM")
end
这些通常被称为 in-place 修改 因为对象本身是被操纵的而不是交换为另一个对象。
让我们开始用更容易理解的方式来分解它。
您可以将 name
(或 value
)等变量与路标进行比较。
假设我将它指向米色房子,这将是变量的值。
考虑到以上内容,让我们看一下第一个示例:
# don't worry about this line for now
House = Struct.new(:color)
def color_red(value)
value.color = :red
end
house = House.new(:beige)
color_red(house)
puts house
# prints: #<struct House color=:red>
那么这里发生了什么?当我们将 house
作为参数传递给 color_red
时,Ruby 将 复制 我们的路标并将其分配给 value
。现在两个路标都指向同一所房子。然后我们按照路标value
的指示走到房子,把它漆成红色。
因此 house
的颜色最终会变成红色。
现在我们来看另一个例子:
def color_red(value)
value = House.new(:red)
end
house = House.new(:beige)
color_red(house)
puts house
# prints: #<struct House color=:beige>
我们从这里开始,我们复制我们的路标house
并将副本分配给value
。然而,我们不会走到房子前去粉刷它,而是要修改路标并将其指向街道下方某处的一所红房子。因为我们的路标 value
是 house
的副本,将其指向新方向不会影响 house
。
您的代码正在做同样的事情。当您调用 value.upcase!
时,您是在对她说字符串。嘿,你,把你所有的角色都大写起来! (类似于粉刷房子。)
当您重新分配 value
(value = "WILLIAM"
) 时,您实际上只是在修改路标并将其指向新的方向。但是,路标作为副本传递,因此不会影响原始路标。
def uppercase(value)
value.upcase!
end
name = 'William'
uppercase(name)
puts name #WILLIAM
在这种情况下,您正在改变原始对象。 name
指向 William
value
也是如此。当你传入参数时,Ruby 将赋值
参数变量 value
指向 name
指向的同一个对象。
def uppercase2(value)
value = "WILLIAM"
end
name = 'William'
uppercase2(name)
puts name
在这种情况下,您正在重新分配 value
。也就是说,你正在改变哪个
对象 value
指向。它指向相同的字符串对象
名字指向。但是,现在,您要求 value
引用一个
不同的对象。
因此,总而言之,upcase!
会改变对象,而 =
会 re-assign。
你可以想到 3 个圆圈,value
在一个圈子里,name
在另一个圈子里,William
在第三。
value
和name
都指向字符串对象,William
.
在第一种情况下,你改变了 value
和 name
的字符串对象
指向。
在第二种情况下,您正在创建第 4 个圆圈,其中包含 WILLIAM
。然后你擦除从 value
到 William
的线并创建一条线
从 value
到 WILLIAM
.
如果我这样做你就明白了:
def uppercase2(value)
value = "WILLIAM"
puts value
end
name = 'William'
uppercase2(name) # => “WILLIAM”
puts name # William
I'm trying to understand the concept of Ruby as a pass-by-reference-value language.
Ruby 是 pass-by-value。总是。它永远不会 pass-by-reference。传递的值是一个不可变的不可伪造的指针,但非常不同于pass-by-reference。
C 是 pass-by-value。 C有指针。这并不意味着如果你在 C 中传递一个指针,它会神奇地变成 pass-by-reference。还是pass-by-value.
C++有指针,和它同时支持pass-by-value和pass-by-reference。在 C++ 中,您可以按值或按引用传递指针,也可以按值或按引用传递 non-pointers。这两个概念是完全正交的。
如果我们可以同意C是pass-by-value,并且我们可以同意C有指针,并且我们可以同意当我们在C中传递一个指针时,它仍然是pass-by-value,那么我们还必须同意 Ruby 是 pass-by-value,因为 Ruby 的行为类似于 C 的假设版本,其中唯一允许的类型是“指向某物的指针”,这是访问值的唯一方法正在取消引用指针,传递值的唯一方法是获取指针。
这不会以任何方式改变在 C 中传递的参数,这意味着它仍然是 pass-by-value,这意味着如果我们在 C pass-by-value 中调用它,调用它没有意义Ruby.
中的任何其他内容def uppercase(value) value.upcase! end name = 'William' uppercase(name) puts name
we get the output "WILLIAM". So value and name are both pointing at the same object, which is initially holding a value of "William" and then .upcase changes it to "WILLIAM". I would understand this as pass-by-reference.
同样,这是 不是 pass-by-reference。 Pass-by-reference 意味着您可以 更改调用方范围内的引用 ,而 Ruby 不允许您这样做。
这只不过是简单的突变。 Ruby 不是纯函数式语言,它确实允许您改变对象。当你改变一个对象时,无论你给它起什么名字,你都可以观察到它改变的状态。
我的朋友叫我“Jörg”,但我的理发师叫我“Mittag 先生”。当我的理发师给我剪头发时,当我遇到我的朋友时它不会神奇地长回来,它仍然会消失,即使他们提到我的名字和我的理发师不一样。
你对同一个对象有两个名字,你改变了那个对象。无论您使用哪个名称来引用该对象,您都将观察到新状态。
那只是“可变状态”,与pass-by-reference无关。
But then if I change
.upcase
to=
:def uppercase2(value) value = "WILLIAM" end name = 'William' uppercase2(name) puts name
The output becomes "William". Which is pass by value.
Why does an assignment operator make the difference in how a variable is treated in Ruby?
没有。两种情况都是pass-by-value。在第二种情况下,您创建了一个新对象并将其分配给 uppercase2
方法内的局部变量 value
。 (从技术上讲,它不是一个局部变量,它是一个参数绑定,但它可以在方法体内被反弹,正是因为Ruby是pass-by-value。如果它是 pass-by-reference,那么这将 也 将 name
局部变量重新分配给新创建的对象。)
有时,pass-by-value 的这种 特定 情况称为 [=,其中传递的值是指向潜在可变对象的不可变指针118=]、call-by-sharing 或 call-by-object。但那是 而不是 不同于 pass-by-value 的东西。还是还是pass-by-value。它是 pass-by-value 的特例,其中值不能是“任何值”,而始终是“不可变的不可伪造的指针”。
有时,您会听到这样的描述“Ruby 是 pass-by-value,其中传递的值是一个引用”或“Ruby 是 pass-by-reference-值”或“Ruby 是 pass-by-value-参考”或“Ruby 是 pass-by-object-reference”。我不太喜欢这些术语,因为它们听起来非常接近“pass-by-reference”,但实际上“pass-by-reference”中的术语“参考”和“pass-by-object-reference”中的术语“参考”表示两个不同的事物。
在“pass-by-reference”中,术语“引用”是一个技术术语,可以认为是对“变量”、“存储位置”等概念的概括。在“pass-by-value-reference”,我们说的是“对象引用”,它更像是指针,但不能被制造或更改。
我也不喜欢上面使用的术语“pass-by-value,其中传递的值是一个不可变的不可伪造的指针”,因为术语“指针”具有一定的含义,尤其是对于来自C、在C中,你可以做指针运算,你可以把一个数字转换成一个指针,即你可以“凭空变出一个指针”。您可以在 Ruby 中完成 none。这就是为什么我添加了形容词“不可变”(没有算术)和“不可伪造”(你不能创建一个指针,只能由系统传递一个),但人们忽视或忽视它们或低估它们的重要性。
在某些语言中,这些指向对象的不可变不可伪造指针被称为“对象引用”(这又是危险的,因为它会引起与“pass-by-reference”的混淆)或 OOPS(Object-Oriented PointerS)具有大多数不受限制的free-for-all“C”指针的不幸含义。 (例如,Go 有更多的限制性指针,但是 whn你只是说“指针”这个词,没有人会想到围棋。)
请注意,其中 none 确实特定于 Ruby。 Python、ECMAScript、Java 和许多其他语言的行为方式相同。 C# 的默认行为方式相同,但也支持 pass-by-reference 作为 显式 opt-in。 (您 必须 在方法定义和方法调用时显式请求 pass-by-reference。)Scala 默认情况下的行为方式相同,但可以选择支持 call-by-name。
C# 实际上是展示这些区别的一种很好的方式,因为 C# 同时支持 pass-by-value 和 pass-by-reference,值类型和引用类型,显然,您可以将类型都编写为可变类型和不可变类型,所以你实际上得到了所有 8 种可能的不同组合,你可以研究它们的行为方式。
我想出了这个简单的测试代码,您也可以轻松地将其翻译成其他语言:
def is_ruby_pass_by_value?(foo)
foo.replace('More precisely, it is call-by-object-sharing!')
foo = 'No, Ruby is pass-by-reference.'
end
bar = 'Yes, of course, Ruby *is* pass-by-value!'
is_ruby_pass_by_value?(bar)
p bar
# 'More precisely, it is call-by-object-sharing!'
Here is the slightly more involved example in C#:
struct MutableCell { public string value; }
static void ArgumentPassing(string[] foo, MutableCell bar, ref string baz, ref MutableCell qux)
{
foo[0] = "More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.";
foo = new string[] { "C# is not pass-by-reference." };
bar.value = "For value types, it is *not* call-by-sharing.";
bar = new MutableCell { value = "And also not pass-by-reference." };
baz = "It also supports pass-by-reference if explicitly requested.";
qux = new MutableCell { value = "Pass-by-reference is supported for value types as well." };
}
var quux = new string[] { "Yes, of course, C# *is* pass-by-value!" };
var corge = new MutableCell { value = "For value types it is pure pass-by-value." };
var grault = "This string will vanish because of pass-by-reference.";
var garply = new MutableCell { value = "This string will vanish because of pass-by-reference." };
ArgumentPassing(quux, corge, ref grault, ref garply);
Console.WriteLine(quux[0]);
// More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.
Console.WriteLine(corge.value);
// For value types it is pure pass-by-value.
Console.WriteLine(grault);
// It also supports pass-by-reference if explicitly requested.
Console.WriteLine(garply.value);
// Pass-by-reference is supported for value types as well.