簡體   English   中英

為什么我們可以在ruby函數中修改復雜的參數,但不能修改標量?

[英]Why can we modify complex parameters but not scalars in a ruby function?

問題Ruby是按引用傳遞還是按值傳遞? 吸引了很多有用的答案,也引起了很多分歧。 到目前為止,我在任何答案中都沒有看到可以解釋以下內容的內容:

ruby -e "def f(x) x=7 end; a=3; f(a); print a"打印3。

ruby -e "def f(x) x[0]=7 end; a=[3]; f(a); print a[0]"打印7。

從經驗上看,在我看來,標量對象與更復雜的對象(例如哈希和數組)之間存在某種區別,標量通過值傳遞,復雜對象通過引用傳遞。 那將類似於C的語義。

我的理解是,紅寶石中的所有東西都是一個對象,對先前問題的回答都沒有提到標量和復雜類型之間的區別。 我的描述是錯誤的嗎?如果是,那么更好的描述是什么?

這里的術語麻煩在於Ruby是“通過對象引用傳遞”,這是用其他語言表達“指向對象的指針”的一種方式。 在Ruby中,指針和引用之間的界限很模糊,因為沒有實際的指針,而且對象本身通過引用計數將指針最終成為引用的位置保存在內存中。 因此,它們是對對象的引用,但不是硬鏈接到相同變量的常規意義上的引用。

根據定義,每個變量始終代表一個對象,即使未定義它: nil本身也是一個對象以及數字,甚至是浮點數。 這使得“標量”一詞幾乎變得無關緊要,Ruby中沒有像其他語言中那樣的基本類型,並且布爾值,數字,字符串和類實例之間的差異被嚴重模糊。

一般規則是,您永遠無法向后傳播對變量的更改,但是通過方法進行的更改會傳播。 要了解原因,以下是Ruby如何看待您的代碼:

def f(x)
  # Change value of local variable x to 7
  x = 7
end

這只是重新定義x指向的對象,因為即使7也是一個對象。

其他代碼在Ruby的感知方式上有根本不同:

def f(x)
  # Send the []= method call to x with the argument 7
  x.send(:[]=, 7)
end

這會向x發送一條消息(方法調用)以觸發[]=方法。 該方法可以使用該值執行任何所需的操作,但是對於數組,散列和具有特定含義的復數而言。 它更新對象x引用的內部狀態。

您可以看到這在其他情況下如何發揮作用:

def f(x)
  x += 'y'
end

這擴展為x = x + y ,它對中間結果進行變量重新分配。 原始x值未修改。

def f(x)
  x << 'y'
end

在這種情況下,是x.send(:<<, 'y')x進行就地修改,因此原始文件將被修改。

在編寫和理解Ruby代碼時,能夠識別方法調用很重要。 有時它們甚至還不那么明顯。 您可能會認為=的存在意味着“變量賦值”,但事實並非如此:

def f(x)
  x.y = 'z'
end

看起來它是分配給xy屬性,但不是,它只是調用y=方法,它等效於x.send(:y=, 'z')x可以用多種方式解釋它。 這可能會修改值,或者可能會做完全不同的事情。 沒有更緊密地了解x ,就無法知道。

從經驗上看,在我看來,標量對象與更復雜的對象(例如哈希和數組)之間存在某種區別,標量通過值傳遞,復雜對象通過引用傳遞。

Ruby中沒有“標量對象”或“復雜對象”之類的東西。 一切都是對象。 期。 一切都是按價值傳遞的,始終都是例外。 永遠不會發生任何通過引用的情況。

更准確地說,Ruby是通常所說的按對象共享,按共享調用或按對象調用。 這是傳遞值的一種特殊情況,其中傳遞的值始終是指向對象的指針。

閉包中的自由變量是通過引用捕獲的,但這是一個不同的問題,與這一問題無關。

那將類似於C的語義。

不,實際上,不會。 C中沒有傳遞引用,C總是像Ruby一樣傳遞值。

在C語言中,一切都是通過值傳遞的。 int通過值傳遞。 char是按值傳遞的。 並且指針按值傳遞。 Ruby和C一樣,只不過有指針。 傳遞的每個值都是指向對象的指針。

 def f(x) x = 7 end a = 3 f(a) a #=> 3 def f(x) x[0] = 7 end a = [3] f(a) a[0] #=> 7 

這兩種情況根本不同:在第一種情況下,您 綁定到方法內部的參數x 此重新綁定僅在方法主體內部可見。 方法參數的行為本質上類似於局部變量。 (實際上,如果您反思方法主體的局部變量,則會看到參數顯示出來。)

在第二種情況下,您調用使接收方發生變化的方法。 沒有正在進行的作業。 是的,有一個等號,但這只是Ruby的索引方法分配語法糖的一部分。 真正在做的是調用方法[]= ,將07作為參數傳遞。 這完全等同於調用x.[]=(0, 7) ; 實際上,您可以根據需要編寫它。 (嘗試一下!)如果您使用insert方法而不是[]= ,或者使用另一個名稱更明顯地尖叫“我正在更改數組”的方法,例如clearreplace ,您可能會不太困惑。

該數組仍然是您傳遞給該方法的完全相同的數組。 參考未修改。 只有陣列了。 如果我們不能將數組插入數組,那么數組將毫無用處,然后數組就留在那里!

因此,這兩種情況之間的區別在於,在第一種情況下,您分配了一個新值,即您對reference進行了突變,這是不起作用的,因為Ruby是按值傳遞的。 在第二種情況下,您對value進行了更改,因為Ruby並不是純函數式語言,它具有純不可變的對象,因此可以工作。 Ruby是不純的,它確實具有可變的對象,如果您對一個對象進行了變異,那么該對象也會發生變異。

我媽媽和我的理發師用不同的名字稱呼我,但是如果我的理發師剪我的頭發,我媽媽也會注意到這一事實。

注意:有些對象沒有使它們變異的方法。 這些對象是不可變的。 Integer就是這樣的不可變對象,因此您永遠無法使用Integer演示類似上述的內容,但這純粹是因為Integer沒有突變方法,與它們的“標量”無關。 如果需要,您可以具有不帶任何突變方法的復雜的復合對象: 這是一個有關在Ruby中實現鏈表的問題 ,這兩個答案包含鏈表的三種實現,它們都是不可變的。 (免責聲明:我有兩個實現的一個答案。)

Ruby是“通過指針傳遞給對象”。

那么,現在有什么區別呢?

def f(x)
  x = 7 
end

為局部變量x分配一個新值-此更改是局部的,因為您已重新分配了局部變量。

def  f(x)
  x[0] = 7
end

通過x向數組引用的第一個元素分配新值-此更改是全局的,因為您修改了對象。

按值傳遞和按引用傳遞之間的區別不適用於Ruby,即來自另一種編程語言,並且在Ruby上下文中沒有意義。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM