1

私はこのコードに匹敵する状況を持っています:

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

なぜ違いがあるのですか?レスキューブロックで実際に変数を変更する関数を取得するにはどうすればよいですか?

4

2 に答える 2

6

これは、add_one関数内で関数外と同じi変数を操作していないためです。

それについて少し説明させてください。Rubyでは、一般的に変更可能なオブジェクトを扱います(注目すべき例外は、数字、、、、ですtrue)。変数は、そのようなオブジェクトへのポインターです。複数の変数が同じオブジェクトを指すことができます。falsenil

a = 123
b = a

ここで、まったく同じオブジェクトaを参照します。またはのいずれかbに新しいオブジェクトを割り当てると、名前を保持したまま、別のオブジェクトを再度参照します。ab

上にあるのはローカル変数です。これらはスコープ内でのみ有効であり、ほとんどの場合、メソッドの期間中です。(値を割り当てることによって)新しいローカル変数を作成する場合、それはメソッドの期間中のみ有効であり、メソッドを離れた後のある時点でガベージコレクションされます。

上で述べたように、Rubyのほとんどのオブジェクトは変更可能です。つまり、変数ポインターを保持したままオブジェクトを変更できます。例として、配列に要素を追加します。

array = []
array << :foo

これで、array変数は初期化されたのと同じ配列オブジェクトを参照します。しかし、あなたはオブジェクトを変更しました。array変数に次のような新しい配列を割り当てる場合

array = [:foo]

同じオブジェクトのように見えますが、事実上、それらは異なります(object_idすべてのオブジェクトでメソッドをチェックしていることを確認できます。同じ場合は、まったく同じオブジェクトを参照しています)

これi += 1で、add_oneメソッドで実行すると、効果的に実行i = i + 1され、ローカルメソッドスコープでi変数が新しい値に設定されます。実際にはi変数を変更していませんが、ローカルメソッドスコープで新しい値を割り当てます。これは、外部スコープ(つまり、開始/終了ブロック)で指定された変数が、メソッド内の変数とi は異なるオブジェクトを参照することを意味します。これは、名前は同じですが、スコープが異なるためです。内側のスコープは常に外側のスコープをマスクするため、異なるスコープに同じ名前の変数がある場合でも、干渉することはありません(これはインスタンス変数またはクラス変数を処理するときに変更されます)iadd_one

残念ながら、上で述べたように、数値は不変です。それらを変更することはできません。変数に新しい番号を割り当てると、それは新しいオブジェクトになります。したがって、値を変更するコードとして、別のスコープ内の数値を指す変数の値を変更することはできません。

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
于 2012-06-19T18:26:44.670 に答える
1

add_oneの「i」は、パラメーターへのローカル参照です。簡単に言うと、これは別の「i」です。

正しいスコープで変数を使用する必要があります。

于 2012-06-19T18:22:25.263 に答える