2012-06-19 20 views
1

我有一種情況是與此代碼相媲美:爲什麼在ruby rescue塊中調用的函數無法修改變量?

i=0 
def add_one(i) 
    i+=1 
    puts "FUNCTION:#{i}" 
end 

begin 
    puts "BEGIN:#{i}" 
    raise unless i>5 
rescue 
    add_one(i) 
    puts "RESCUE:#{i}" 
    retry 
end 

當我運行此,我多次看到這樣的輸出:

BEGIN:0 
FUNCTION:1 
RESCUE:0 

這個版本的增量i和完美的完成了計劃:

i=0 
begin 
    puts "BEGIN:#{i}" 
    raise unless i>5 
rescue 
    i+=1 
    puts "RESCUE:#{i}" 
    retry 
end 

爲什麼會有差異?如何在救援塊中獲得一個函數來實際修改一個變量?

回答

5

這是因爲在你的add_one函數中,你並沒有在函數外部操縱相同的i變量。

讓我試着解釋一下。在Ruby中,你處理的是一般可變的對象(值得注意的例外是數字,true,falsenil)。變量是指向這樣一個對象的指針。多個變量可以指向同一個對象。

a = 123 
b = a 

現在ab指的是相同的對象。如果您將新對象分配給ab之一,則它們將再次引用不同的對象,同時仍保留該名稱。

你上面的是局部變量。這些僅在範圍內有效,主要是方法的持續時間。如果你創建一個新的局部變量(通過賦值給它),它只會在方法持續時間內有效,並且在離開方法後的某個時候會被垃圾回收。

現在正如我上面所說,Ruby中的大多數對象都是可變的,這意味着您可以在保留變量指針的同時更改它們。一個例子是將一個元素增加到數組:

array = [] 
array << :foo 

現在array變量仍然會引用它得到了與初始化相同的陣列對象。但是你已經改變了這個對象。如果你將一個新的數組分配array變量像

array = [:foo] 

它看起來像同一個對象,但實際上,它們是不同的(你可以驗證來檢查每個對象的object_id方法。如果是同樣的,你指的是相同的對象)

現在,當你在add_one方法做i += 1時,實際上是運行i = i + 1將變量i設置爲在本地方法範圍的新值。你實際上並沒有改變i變量,而是在本地方法範圍內爲它分配一個新值。這意味着您的外部範圍名爲i的變量(即開始/結束塊)將引用與您的add_one方法中的變量i不同的對象。這是因爲雖然他們有相同的名字,但他們有不同的範圍。內部作用域始終掩蓋外部作用域,因此當您在不同作用域中具有相同名稱的變量時,它們不會以任何方式干涉(在處理實例或類變量時會發生變化)

不幸的是,正如我上面所說,數字是不可改變的。你不能改變它們。如果你給一個變量賦一個新的數字,它就是一個新的對象。因此,您不能將指向另一個作用域中某個數字的變量的值更改爲更改該值的代碼。

爲了規避這個問題,可以從您的函數返回一個值,並明確地將其分配給您的i變量在外部範圍這樣

i = 0 

def add_one(i) 
    i+=1 
    puts "FUNCTION:#{i}" 
    return i 
end 

i = add_one(i) 

或者你可以使用實例變量的對象是這樣

class Foo 
    def initialize 
    @i = 0 
    end 

    def add_one 
    @i += 1 
    end 

    def do_something 
    begin 
     puts "BEGIN:#{@i}" 
     raise unless @i>5 
    rescue 
     add_one 
     puts "RESCUE:#{@i}" 
     retry 
    end 
    end 
end 

# create a new object and run the instance method 
Foo.new.do_something 
+0

感謝您的詳細解釋 - 我現在明白了很多! –

1

add_one中的「i」是本地引用,對於參數 - 簡而言之就是它是不同的「i」。

你需要在正確的範圍內使用一個變量。

相關問題