提问人:Sarah Diri 提问时间:10/28/2020 更新时间:10/29/2020 访问量:1149
为什么您可以使用其他方法更改函数中 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 的概念理解为一种按引用传递值的语言。使用我在这个网站上找到的一个例子......
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
如果要替换该文本,则需要对对象使用方法来影响它(如果支持)。在这种情况下,有一种方法:
def uppercase2(value)
value.replace("WILLIAM")
end
这些通常称为就地修改,因为对象本身是纵的,而不是交换为另一个对象。
评论
value[0..-1] = "WILLIAM"
self
object_id
String
让我们开始用更易于理解的东西来分解它。
您可以将变量(或)与路标进行比较。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>
那么这里会发生什么?当我们将参数传递给 Ruby 时,它将复制我们的路标并将其分配给 .现在两个路标都指向同一所房子。然后我们按照路标的指示走到房子前,把它涂成红色。house
color_red
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
在这种情况下,您正在改变原始对象。 指向,也是如此。当你传入参数时,Ruby 将分配
指向的同一对象的参数变量。name
William
value
value
name
def uppercase2(value)
value = "WILLIAM"
end
name = 'William'
uppercase2(name)
puts name
在本例中,您将重新分配 。也就是说,您正在更改哪个
对象指向。它指向同一个字符串对象
名字指向。但是,现在,您要求引用
不同的对象。value
value
value
因此,总而言之,在重新分配对象时会改变对象。
你可以想到 3 个圆圈,在一个圆圈中,在另一个圆圈中,在第三个圆圈中。 并且两者都指向 String 对象 。upcase!
=
value
name
William
value
name
William
在第一种情况下,您改变 和 都指向的字符串对象。value
name
在第二种情况下,您正在创建第 4 个圆圈,其中包含。然后,您将从 to 擦除线并创建一条线
从 到 .WILLIAM
value
William
value
WILLIAM
如果我这样做,你就会明白:
def uppercase2(value)
value = "WILLIAM"
puts value
end
name = 'William'
uppercase2(name) # => “WILLIAM”
puts name # William
我试图将 Ruby 的概念理解为一种按引用传递值的语言。
Ruby 是按值传递的。总是。它绝不是按引用传递的。传递的值是不可变的不可伪造指针,但这与按引用传递有很大不同。
C 是按值传递的。C 有指针。这并不意味着如果你在 C 语言中传递一个指针,它就会神奇地变成按引用传递。它仍然是按值传递的。
C++ 具有指针,并且支持按值传递和按引用传递。在 C++ 中,可以按值或引用传递指针,也可以按值或引用传递非指针。这两个概念是完全正交的。
如果我们同意 C 是按值传递的,我们可以同意 C 有指针,我们可以同意当我们在 C 中传递一个指针时,它仍然是按值传递的,那么我们也必须同意 Ruby 是按值传递的,因为 Ruby 的行为类似于 C 的假设版本,其中唯一允许的类型是“指向某物的指针”, 访问值的唯一方法是取消引用指针,而传递值的唯一方法是获取指针。
这不会以任何方式改变在 C 中传递的参数,这意味着它仍然是按值传递的,这意味着如果我们在 C 中调用它按值传递,那么在 Ruby 中调用它没有其他任何意义。
def uppercase(value) value.upcase! end name = 'William' uppercase(name) puts name
我们得到输出“WILLIAM”。因此,value 和 name 都指向同一个对象,该对象最初持有值“William”,然后 .upcase 将其更改为“WILLIAM”。我会将其理解为传递参考。
同样,这不是通过引用传递。传递引用意味着您可以更改调用方作用域中的引用,而 Ruby 不允许您这样做。
这只不过是简单的突变。Ruby 不是一种纯粹的函数式语言,它确实允许你改变对象。当你改变一个对象时,无论你叫什么名字,你都可以观察它的变化状态。
我的朋友叫我“Jörg”,但我的理发师叫我“米塔格先生”。当我的理发师剪我的头发时,当我遇到我的朋友时,它不会神奇地长回来,即使他们不像我的理发师那样用同样的名字来称呼我,它仍然会消失。
同一对象有两个名称,并且对该对象进行变异。无论您使用哪个名称来引用该对象,您都将观察到新状态。
这只是“可变状态”,它与按引用传递无关。
但是,如果我改为:
.upcase
=
def uppercase2(value) value = "WILLIAM" end name = 'William' uppercase2(name) puts name
输出变为“William”。这是按值传递的。
为什么赋值运算符会影响 Ruby 中变量的处理方式?
事实并非如此。这两种情况都是按值传递的。在第二种情况下,您创建了一个新对象,并将其分配给方法内部的局部变量。(从技术上讲,它不是一个局部变量,它是一个参数绑定,但它可以在方法体内部反弹,正是因为 Ruby 是按值传递的。如果它是按引用传递的,那么这也将把局部变量重新分配给新创建的对象。value
uppercase2
name
有时,这种按值传递的特定情况(其中传递的值是指向潜在可变对象的不可变指针)称为逐对象调用共享、逐个共享调用或按对象调用。但这与按价值传递没有什么不同。它仍然是按值传递的。这是按值传递的一种特殊情况,其中值不能是“任何值”,而始终是“不可变的不可伪造指针”。
有时,您会听到这样的描述:“Ruby 是按值传递的,其中传递的值是引用”或“Ruby 是按引用传递值”或“Ruby 是按值引用传递”或“Ruby 是按对象引用传递”。我不太喜欢这些术语,因为它们听起来非常接近“传递引用”,但实际上“传递引用”中的术语“引用”和“传递对象引用”中的术语“引用”意味着两个不同的东西。
在“通过引用”中,术语“参考”是一个技术术语,可以被认为是“变量”、“存储位置”等概念的概括。在“传递值引用”中,我们谈论的是“对象引用”,它更像是指针,但不能制造或更改。
我也不喜欢我上面使用的术语“按值传递,其中传递的值是不可变的不可伪造的指针”,因为术语“指针”有一定的内涵,特别是对于来自 C 的人。在 C 语言中,你可以做指针算术,你可以向指针投射一个数字,即你可以“凭空变出一个指针”。在 Ruby 中,你什么都做不到。这就是为什么我添加了形容词“不可变”(没有算术)和“不可伪造”(你不能创建一个指针,只能由系统传递一个指针),但人们忽略或忽略它们或低估它们的重要性。
在某些语言中,这些指向对象的不可变的不可伪造指针被称为“对象引用”(这又是危险的,因为它会引起与“按引用传递”的混淆)或 OOPS(面向对象的指针),它具有大多数不受限制的自由“C”指针的不幸含义。(例如,Go 的指针限制性要强得多,但当你简单地说出“指针”这个词时,没有人会想到 Go。
请注意,这些都不是 Ruby 特有的。Python、ECMAScript、Java 和许多其他方法的行为方式相同。默认情况下,C# 的行为方式相同,但也支持按引用传递作为显式选择加入。(您必须在方法定义和方法调用时显式请求按引用传递。默认情况下,Scala 的行为方式相同,但可以选择支持按名称调用。
C# 实际上是演示这些区别的一个非常好的方法,因为 C# 支持按值传递和按引用传递,既支持值类型,也支持引用类型,显然,您可以将类型编写为可变类型和不可变类型,因此您实际上可以获得所有 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!'
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 会重新分配变量。value = "WILLIAM"
self