Ruby字符串作为函数参数是按引用传递的

6

我是一个Ruby新手

我知道Ruby在函数参数传递时采用的是引用传递

但是,我感觉这与常规的C/C++风格的引用传递略有不同

示例代码:

def test1(str)
    str += ' World!'
end

def test2(str)
    str << ' World!'
end

str = 'Hello'

test1(str)
p str # Hello

test2(str)
p str # Hello World!

如果我在使用C/C++中的引用,我期望test1也能返回Hello World!。这只是出于好奇,任何解释都会被赞赏。

2
你的理解是错误的 - Ruby 是按值传递的,尽管这个概念并不完全适用。请参阅 https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_sharing - Stefan
5个回答

4
我了解到Ruby对于函数参数采用的是严格的传值方式。Ruby从来不会采用传引用方式。
这只是出于好奇,如果有任何解释将不胜感激。
关于为什么你的代码片段不能展示你所期望的传引用结果的简单解释是Ruby不是传引用,而是传值方式,你的代码片段证明了这一点。
以下是一个小示例,演示了Ruby实际上采用的是传值而不是传引用:
#!/usr/bin/env ruby

def is_ruby_pass_by_value?(foo)
  foo << <<~HERE
    More precisely, it is call-by-object-sharing!
    Call-by-object-sharing is a special case of pass-by-value, 
    where the value is always an immutable pointer to a (potentially mutable) value.
  HERE
  foo = 'No, Ruby is pass-by-reference.'
  return
end

bar = ['Yes, of course, Ruby *is* pass-by-value!']

is_ruby_pass_by_value?(bar)

puts bar
# Yes, of course, Ruby *is* pass-by-value!,
# More precisely, it is call-by-object-sharing!
# Call-by-object-sharing is a special case of pass-by-value, 
# where the value is always an immutable pointer to a (potentially mutable) value.

Ruby允许对象的变异,但它并不像Haskell或Clean一样是纯函数式语言。


3
我对这个问题的解释有所不同。OP 多次谈到了按引用传递(pass-by-reference),他明确问道为什么 test1 不能展示按引用传递,尽管有人告诉他 Ruby 是按引用传递的。而对于这个问题的正确答案是相信代码,它清楚地展示了 Ruby 不是按引用传递的(否则 OP 的代码就能按预期工作),而不是相信告诉他 Ruby 是按引用传递的人。下面是一个小例子,展示了 Ruby 是按值传递(pass-by-value),而不是按引用传递。 - Jörg W Mittag
1
只是为了确保我理解正确...所以ruby(就像java一样)是按值传递,因为对象指针被复制到参数中。因此,第一个示例不起作用,因为它将另一个值分配给指针。然而,第二个示例有效,因为它操作指针指向的对象。我的理解正确吗? - jrhee17
1
我认为,“Ruby是传值调用”这个说法是一个半真实的陈述?误导性的?不完整的?正确的说法应该是,“Ruby是传值调用,但它传递的值是引用。”而这第二部分则隐藏在你片段的注释中。 - x-yuri
1
对于一个来自C*的人来说,这可能是非常误导性的。但是对我来说,按值传递至少也是具有误导性的。尽管我认为我知道它在那些语言中是如何工作的。在那里,如果你通过引用传递,你传递的是一个变量的引用(而不是变量所持有的值,这反过来可能是一个引用),因此被调用者可以改变变量所持有的值(而不仅仅是变量所引用的值)。对我来说,“Ruby是按值传递”的说法是具有误导性的,因为当我听到“按值传递”时,首先想到的是,“哦,被调用者不能更改我传递的内容”。... - x-yuri
1
让我这样尝试一下,“在Ruby中,每个变量都保存着一个值的引用。从技术上讲,Ruby是按值传递的。但是,在“按值传递”中的“值”始终是一个引用。因此,被调用者可以更改参数所引用的值,但不能更改参数保存的值(它始终是一个引用)... - x-yuri
显示剩余5条评论

1
在第一种情况下,当您执行str += ' World!'时,将创建一个新对象。
str = "Hello"
=> "Hello"
str.object_id
=> 69867706917360
str += " World"
=> "Hello World"
str.object_id
=> 69867706885680

str = "Hello"
=> "Hello"
str.object_id
=> 69867706856200
str << " World"
=> "Hello World"
str.object_id
=> 69867706856200

str = "Hello"
=> "Hello"
str.object_id
=> 69867706786780
str.freeze
=> "Hello"
str << " World"
RuntimeError: can't modify frozen String
str += " World"
=> "Hello World"

"<<" 是一个二进制左移运算符。左操作数的值向左移动右操作数指定的位数。

因此,"<<" 不会创建新的字符串,str.contact("World") 也不会创建新的字符串。 方法 test1 不需要对返回结果做任何处理,您可以尝试这个方法:

def test1(str)
    str.concat(' World!')
end

"<< "是二进制左移位运算符",但这里不是。它只是一个字符串的方法,用于修改它(附加另一个字符串)。 - steenslag

0

字符串被引用,在Ruby中,除了像数字、truefalsenil这样的值之外,其他都被引用。

a = "hello"
b = a
a.replace("Hola")
p a # Hola
p b # Hola

你需要在开头添加魔法注释。
# frozen_string_literal: true
a = "hello"
b = a
a.replace("Hola") #  can't modify frozen String: "hello" (FrozenError)


0

看一下你的测试的以下改编,通过显示对象的object_id,您可以轻松地看到它是否相同。Test1由于+=连接而返回另一个String对象,但之后没有使用它。 这看起来像是按引用传递,但实际上传递的是指向对象的指针的值。我能找到的最好的解释是这里,作者称其为按值传递引用。

def test1(str)
    p ["in test1 before", str.object_id]
  str += ' World!'
  p ["in test1 after", str.object_id]
  str
end

def test2(str)
    p ["in test2", str.object_id]
  str << ' World!'
end

str = 'Hello'
p ["in main", str.object_id]

test1(str)
p str # Hello
p ["after test1", str.object_id]

test2(str)
p str 
p ["after test2", str.object_id]

给予

["in main", 12363600]
["in test1 before", 12363600] # the same object, so pointer to object passed by value    
["in test1 after", 12362976] # returns a new object, the old is unchanged
"Hello"
["after test1", 12363600] # idem
["in test2", 12363600]
"Hello World!"
["after test2", 12363600]
# still the same object

-2
def test1(str)
    str += ' World!'
end

+= 运算符是 Ruby 中的一种语法糖。表达式 a += b 相当于 a = a + b。对于 String 实例,运用 + 运算符会创建一个新的字符串,该字符串是两个参数的连接。这就是为什么在第一个情况下 str 没有被修改的原因。

此外,我想更正你的说法:

我知道 Ruby 对函数参数采用按引用传递的方式

实际上,Ruby 对每个参数都采用按引用传递的方式,除了“值类型”——即值为 niltruefalse 和类 Fixnum 的实例。


2
你混淆了引用传递和值传递。前者允许方法修改传递的变量,即为其分配另一个值。但在 Ruby 中这是不可能的,因为你没有接收到对该变量的引用。从这个意义上说,Ruby 是值传递——通过对 str 进行赋值,你只是改变了本地变量,而没有影响调用方作用域内的变量。 - Stefan
2
test2 修改了变量所引用的对象,但没有修改变量本身。按引用传递 意味着该方法可以修改变量,即它可以通过将另一个值分配给传递的参数来更改调用者的变量。 - Stefan
@Stefan - 抱歉,但您似乎对这个主题有些困惑。按引用传递 意味着该方法可以修改 _对象_,因为它具有指向该对象的引用(指针)。这正是 test2 函数所做的。它修改了初始值为“Hello”的字符串,该字符串是 按引用传递 到该函数中的。 - petrch
2
如果 Ruby 是按引用传递的,那么 OP 的代码就可以工作了,这个问题也不会存在。 - Jörg W Mittag
2
@petrch:不,传递引用意味着该方法可以修改引用。修改对象是可变性的问题。这里没有人声称Ruby是一种纯函数语言,不允许修改对象。它显然是可以的。 - Jörg W Mittag
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接