微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

为什么您可以使用其他方法而不使用赋值运算符在函数中更改Ruby中局部变量的值?

如何解决为什么您可以使用其他方法而不使用赋值运算符在函数中更改Ruby中局部变量的值?

我正在尝试理解Ruby作为传递参考值语言的概念。使用我在this网站上找到的示例...

def uppercase(value)
  value.upcase!
end

name = 'William'
uppercase(name)
puts name

我们得到输出“ WILLIAM”。因此,值和名称都指向同一对象,该对象最初持有值“ William”,然后.upcase将其更改为“ WILLIAM”。我将其理解为通过引用。

但是如果我将.upcase更改为=

def uppercase2(value)
  value = "WILLIAM"
end

name = 'William'
uppercase2(name)
puts name

输出变为“ William”。这是按值传递的。

为什么赋值运算符在Ruby中如何对待变量方面有所不同?

解决方法

这里的关键是您永远不能更改Ruby对象的核心self,一旦创建它,​​它将一直是同一对象,直到垃圾被收集和销毁为止。只能更改对象的 properties

但是,您可以更改变量或对象引用,例如实例变量,常量,attr_accessor属性等。

所以在这种情况下:

def uppercase2(value)
  value = "WILLIAM"
end

这将重新分配value局部变量。它对原始的对象没有任何作用。

如果要替换该文本,则需要使用对象上的方法来实现(如果支持)。在这种情况下,有一种方法:

def uppercase2(value)
  value.replace("WILLIAM")
end

这些通常称为就地修改,因为对象本身是受操纵的,而不是交换给另一个对象。

,

我正在尝试理解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”。因此,值和名称都指向同一对象,该对象最初持有值“ William”,然后.upcase将其更改为“ WILLIAM”。我将其理解为通过引用。

同样,这是不是传递参考。引用传递意味着您可以在调用者的作用域中更改引用,而Ruby不允许您这样做。

这不过是简单的突变。 Ruby不是纯粹的功能语言,它确实允许您更改对象。而且,当您对一个对象进行突变时,无论您叫什么名字,都可以观察到它的改变状态。

我的朋友称我为“Jörg”,但理发师称我为“ Mittag先生”。当我的理发师剪头发时,当我遇见我的朋友时,它并不会神奇地恢复原状,即使他们没有像我的理发师那样称呼我,它也仍然会消失。

对于同一对象,您有两个名称,然后对该对象进行突变。无论您使用哪个名称来引用该对象,您都将观察到新状态。

那只是“可变状态”,与传递引用无关。

但是如果我将.upcase更改为=

def uppercase2(value)
  value = "WILLIAM"
end

name = 'William'
uppercase2(name)
puts name

输出变为“ William”。这是按值传递的。

为什么赋值运算符在Ruby中如何对待变量方面有所不同?

不是。两种情况都是按值传递。在第二种情况下,您创建了一个新对象,并将其分配给value方法内的局部变量uppercase2。 (从技术上讲,它不是局部变量,而是参数绑定,但是它可以在方法主体内反弹,正是因为 Ruby是按值传递的。如果它是按值传递的,引用,那么 也会将name局部变量重新分配给新创建的对象。)

有时候,这种特定传递值的情况,其中传递的值是指向潜在可变对象的不可变指针,称为按对象共享调用 em>,共享调用对象调用。但这与传值不同。这是静止传递值。这是传值的一种特殊情况,该值不能为“任何值”,但始终为“不变的,不可伪造的指针”。

有时,您会听到以下描述:“ Ruby是传递值,其中传递的值是引用”或“ Ruby是传递参考值”或“ Ruby是传递值” -reference”或“ Ruby是通过对象引用”。我不太喜欢这些术语,因为它们声音非常接近“通过引用”,但实际上是“通过引用”中的“参考”和“引用” “通过对象引用”中的“ 是指两个不同的东西

在“通过引用”中,术语“引用”是一个技术术语,可以认为是“变量”,“存储位置”等概念的概括。 -value-reference”,我们所说的是“对象引用”,它更像指针,但不能制造或更改。

我也不喜欢在“传递值时传递的值是不可变的,不可伪造的指针”上方使用的术语,因为术语“指针”具有某些含义,特别是对于来自C语言的人。在C语言中,您可以执行指针算术,并且可以将数字强制转换为指针,即可以“凭空想出一个指针”。您无法在Ruby中执行任何操作。这就是为什么我添加形容词“不可变”(不算术)和“不可伪造”(您不能创建指针,只能由系统处理),但是人们却忽略或忽略了它们或低估了它们的重要性。

在某些语言中,这些指向对象的不可变的不可伪造的指针称为“对象引用”(再次危险,因为它会引起与“传递引用”的混淆)或具有不幸含义的OOPS(面向对象的指针)大部分不受限制的“ C”指针。 (例如,Go具有更多限制性的指针,但是当您只说“ pointer”一词时,没人会想到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!'

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,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,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.
,

让我们开始以更容易理解的方式对其进行分解。

您可以将变量name(或value)与路标进行比较。

signpost

比方说,我将其指向米色的房子,这将是变量的值。

beige house

考虑到以上几点,让我们看一下第一个示例:

# 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。但是,我们没有走到屋子上粉刷它,而是要修改路标,并将其指向街上更远的红色房屋。而且由于我们的路标valuehouse的副本,因此将其指向新的方向不会影响house


您的代码正在执行相同的操作。当您致电value.upcase!时,您是在对她说。嘿,你把所有角色都改成大写! (类似于给房子粉刷。)

重新分配valuevalue = "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!会变异对象,而=将重新分配。
您可以想到3个圈子,value在一个圆圈中,name在另一个圆圈中,William 在第三。 valuename都指向字符串对象William

在第一种情况下,您将valuename的字符串对象都进行了突变 指向。

在第二种情况下,您正在创建第四个圆,其中有WILLIAM。然后,您要擦除从valueWilliam的那一行并创建一行 从valueWILLIAM

如果我这样做,您就会明白:

def uppercase2(value)
  value = "WILLIAM"
  puts value
end

name = 'William'
uppercase2(name) # => “WILLIAM”
puts name           # William

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。