Ruby ผ่านการอ้างอิงหรือตามค่าหรือไม่


248
@user.update_languages(params[:language][:language1], 
                       params[:language][:language2], 
                       params[:language][:language3])
lang_errors = @user.errors
logger.debug "--------------------LANG_ERRORS----------101-------------" 
                + lang_errors.full_messages.inspect

if params[:user]
  @user.state = params[:user][:state]
  success = success & @user.save
end
logger.debug "--------------------LANG_ERRORS-------------102----------" 
                + lang_errors.full_messages.inspect

if lang_errors.full_messages.empty?

@userวัตถุเพิ่มข้อผิดพลาดให้กับlang_errorsตัวแปรในupdate_lanugagesวิธีการ เมื่อฉันทำการบันทึกบน@userวัตถุฉันสูญเสียข้อผิดพลาดที่เก็บไว้ในlang_errorsตัวแปรเริ่มแรก

แม้ว่าสิ่งที่ฉันพยายามทำจะเป็นมากกว่าแฮ็ค (ซึ่งดูเหมือนจะไม่ทำงาน) ฉันต้องการที่จะเข้าใจว่าทำไมค่าตัวแปรจึงถูกล้างออก ฉันเข้าใจการอ้างอิงผ่านดังนั้นฉันอยากทราบว่าค่าสามารถเก็บไว้ในตัวแปรนั้นได้อย่างไรโดยไม่ถูกชะล้างออกไป


ฉันยังสังเกตเห็นว่าผมสามารถที่จะรักษาค่านั้นในวัตถุโคลน
ซิด

1
คุณควรดูคำตอบ Abe Voelker แต่หลังจากวิ่งไปรอบ ๆ บล็อกนี้นี่คือวิธีที่ฉันจะพูดมัน เมื่อคุณส่งวัตถุ Foo ไปยังกระบวนการสำเนาของการอ้างอิงไปยังวัตถุจะถูกส่งผ่านแถบผ่านค่า คุณไม่สามารถเปลี่ยนวัตถุที่ Foo ชี้ไป แต่คุณสามารถเปลี่ยนเนื้อหาของวัตถุที่ชี้ไป ดังนั้นถ้าคุณผ่านอาร์เรย์เนื้อหาของอาร์เรย์สามารถเปลี่ยนแปลงได้ แต่คุณไม่สามารถเปลี่ยนแปลงอาร์เรย์ที่อ้างอิงได้ ยินดีที่ได้ใช้วิธีการของ Foo โดยไม่ต้องกังวลกับการยุ่งเกี่ยวกับการพึ่งพาอื่น ๆ ของ Foo
bobbdelsol

คำตอบ:


244

ในคำศัพท์แบบดั้งเดิมทับทิมเป็นอย่างเคร่งครัดผ่านโดยค่า แต่นั่นไม่ใช่สิ่งที่คุณถามที่นี่

Ruby ไม่มีแนวคิดเกี่ยวกับค่าที่บริสุทธิ์และไม่มีการอ้างอิงดังนั้นคุณจะไม่สามารถส่งผ่านหนึ่งวิธีไปได้ ตัวแปรอ้างอิงถึงวัตถุอยู่เสมอ ในการรับวัตถุที่จะไม่เปลี่ยนแปลงจากคุณคุณจำเป็นต้องทำซ้ำหรือคัดลอกวัตถุที่คุณส่งผ่านไปดังนั้นให้วัตถุที่ไม่มีใครมีการอ้างอิงถึง (แม้จะไม่ได้เป็นกระสุน แต่ทั้งสองวิธีการโคลนมาตรฐานจะทำสำเนาแบบตื้นดังนั้นตัวแปรอินสแตนซ์ของโคลนยังคงชี้ไปที่วัตถุเดียวกันกับที่ต้นฉบับทำถ้าวัตถุที่อ้างอิงโดย ivars จะกลายพันธุ์ ยังคงปรากฏในสำเนาเนื่องจากอ้างอิงวัตถุเดียวกัน)


88
ทับทิมผ่านโดยค่า ไม่เลย ไม่เป็นไร ไม่มีข้อยกเว้น. หากคุณต้องการทราบว่าทับทิม (หรือภาษาอื่น ๆ ) เป็นผ่านโดยการอ้างอิงหรือผ่านโดยมีมูลค่าdef foo(bar) bar = 'reference' end; baz = 'value'; foo(baz); puts "Ruby is pass-by-#{baz}"เพียงแค่พยายามที่จะออก:
Jörg W Mittag

95
@ JörgWMittag: ใช่ แต่ความสับสนของ OP ไม่ได้ผ่านการอ้างอิงหรือการอ้างอิงผ่านในความหมาย CS ที่เข้มงวดของคำ สิ่งที่เขาหายไปคือ "คุณค่า" ที่คุณส่งผ่านไปคือการอ้างอิง ฉันรู้สึกว่าเพียงแค่พูดว่า "มันผ่านไปแล้วตามตัวอักษร" จะอวดดีและทำให้ OP เป็นความเสียหายเพราะนั่นไม่ใช่สิ่งที่เขาต้องการจริงๆ แต่ขอขอบคุณสำหรับความกระจ่างเพราะมันเป็นสิ่งสำคัญสำหรับผู้อ่านในอนาคตและฉันควรจะรวมไว้ (ฉันฉีกขาดระหว่างเสมอรวมถึงข้อมูลเพิ่มเติมและไม่ทำให้เกิดความสับสนคน.)
ชัค

16
ไม่เห็นด้วยกับ @Jorg Ruby ผ่านการอ้างอิงเขาเพิ่งเปลี่ยนการอ้างอิง ลองทำสิ่งนี้แทน: def foo (bar) bar.replace 'reference' end; baz = 'value'; foo (Baz); ใส่ "Ruby is pass-by - # {baz}"
pguardiario

15
@ pguardiario: ฉันคิดว่ามันเป็นแค่คำถามของคำจำกัดความ คุณกำลังใช้คำจำกัดความของ "การอ้างอิงโดยอ้อม" ที่คุณคิดขึ้นเองในขณะที่Jörgใช้นิยามวิทยาศาสตร์คอมพิวเตอร์แบบดั้งเดิม แน่นอนว่าไม่มีธุรกิจของฉันที่จะบอกคุณถึงวิธีการใช้คำศัพท์ - ฉันแค่คิดว่ามันสำคัญที่จะอธิบายว่าคำศัพท์ปกติหมายถึงอะไร ในศัพท์ดั้งเดิม Ruby คือการส่งต่อค่า แต่ค่าเหล่านั้นเป็นการอ้างอิง ฉันเข้าใจอย่างถ่องแท้ว่าทำไมคุณและ OP ชอบคิดว่านี่เป็นแบบอ้างอิงโดยอ้างอิง - ไม่ใช่ความหมายดั้งเดิมของคำ
Chuck

7
ทุกอย่างใน Ruby เป็นวัตถุดังนั้น Ruby จึงไม่ผ่านค่าหรือผ่านการอ้างอิงอย่างน้อยก็ในแง่ที่คำเหล่านั้นถูกใช้ใน C ++ "ส่งผ่านการอ้างอิงวัตถุ" อาจเป็นวิธีที่ดีกว่าในการอธิบายสิ่งที่ทับทิมทำ ในท้ายที่สุดแม้ว่าทางออกที่ดีที่สุดอาจไม่ให้ความหมายมากเกินไปกับเงื่อนไขเหล่านี้และเพียงแค่ได้รับความเข้าใจที่ดีเกี่ยวกับพฤติกรรมที่เกิดขึ้นจริง
David Winiecki

424

ผู้ตอบคนอื่นนั้นถูกต้องทั้งหมด แต่เพื่อนขอให้ฉันอธิบายเรื่องนี้ให้เขาฟังและสิ่งที่จริง ๆ แล้วมันลดลงถึงวิธีการที่ Ruby จัดการกับตัวแปรดังนั้นฉันคิดว่าฉันจะแบ่งปันรูปภาพ / คำอธิบายง่ายๆที่ฉันเขียนให้เขา และอาจจะมีการใช้งานเกินขนาด):


Q1: เกิดอะไรขึ้นเมื่อคุณกำหนดตัวแปรใหม่strเป็นค่าของ'foo'?

str = 'foo'
str.object_id # => 2000

ป้อนคำอธิบายรูปภาพที่นี่

A: A ฉลากที่เรียกว่าstrถูกสร้างขึ้นที่จุดวัตถุซึ่งรัฐนี้ล่ามทับทิมเกิดขึ้นเป็นที่ตั้งของหน่วยความจำ'foo'2000


Q2: เกิดอะไรขึ้นเมื่อคุณกำหนดตัวแปรที่มีอยู่strไปยังวัตถุใหม่โดยใช้=?

str = 'bar'.tap{|b| puts "bar: #{b.object_id}"} # bar: 2002
str.object_id # => 2002

ป้อนคำอธิบายรูปภาพที่นี่

ตอบ: ฉลากstrตอนนี้ชี้ไปที่วัตถุอื่น


Q3: จะเกิดอะไรขึ้นเมื่อคุณกำหนดตัวแปรใหม่ =เพื่อstr?

str2 = str
str2.object_id # => 2002

ป้อนคำอธิบายรูปภาพที่นี่

A: A ฉลากใหม่ที่เรียกว่าstr2ถูกสร้างขึ้นที่จุดที่วัตถุเดียวกันstrเป็น


Q4: จะเกิดอะไรขึ้นถ้าวัตถุที่อ้างอิงถึง strและstr2ได้รับการเปลี่ยนแปลง?

str2.replace 'baz'
str2 # => 'baz'
str  # => 'baz'
str.object_id # => 2002
str2.object_id # => 2002

ป้อนคำอธิบายรูปภาพที่นี่

ตอบ: ป้ายทั้งสองยังคงชี้ไปที่วัตถุเดียวกัน แต่วัตถุนั้นกลายพันธุ์ (เนื้อหาได้เปลี่ยนเป็นอย่างอื่น)


สิ่งนี้เกี่ยวข้องกับคำถามเดิมอย่างไร

โดยพื้นฐานแล้วมันเหมือนกับสิ่งที่เกิดขึ้นใน Q3 / Q4; เมธอดได้รับสำเนาตัวแปร / เลเบล ( str2) ของตัวเองที่ผ่านเข้าไปในนั้น ( str) มันไม่สามารถเปลี่ยนวัตถุป้ายstr ชี้ไปแต่ก็สามารถเปลี่ยนเนื้อหาของวัตถุที่พวกเขาทั้งสองจะมีการอ้างอิงอื่น:

str = 'foo'

def mutate(str2)
  puts "str2: #{str2.object_id}"
  str2.replace 'bar'
  str2 = 'baz'
  puts "str2: #{str2.object_id}"
end

str.object_id # => 2004
mutate(str) # str2: 2004, str2: 2006
str # => "bar"
str.object_id # => 2004

1
Robert Heaton บล็อกเมื่อเร็ว ๆ นี้ด้วยเช่นกัน: robertheaton.com/2014/07/22/ …
Michael Renner

48

Ruby ใช้ "การอ้างอิงผ่านวัตถุ"

(การใช้คำศัพท์ของ Python)

ในการพูดว่า Ruby ใช้ "pass by value" หรือ "pass by reference" นั้นไม่ได้มีความหมายเพียงพอที่จะเป็นประโยชน์ ฉันคิดว่าเป็นคนส่วนใหญ่รู้ว่าวันนี้คำศัพท์นั้น ("ค่า" กับ "การอ้างอิง") ​​มาจาก C ++

ใน C ++ "pass by value" หมายถึงฟังก์ชั่นได้รับสำเนาของตัวแปรและการเปลี่ยนแปลงใด ๆ ในการคัดลอกจะไม่เปลี่ยนต้นฉบับ นั่นเป็นความจริงสำหรับวัตถุด้วย หากคุณส่งตัวแปรออบเจ็กต์ตามค่าดังนั้นออบเจ็กต์ทั้งหมด (รวมถึงสมาชิกทั้งหมด) จะถูกคัดลอกและการเปลี่ยนแปลงใด ๆ กับสมาชิกจะไม่เปลี่ยนสมาชิกเหล่านั้นบนออบเจ็กต์ดั้งเดิม (มันแตกต่างกันถ้าคุณผ่านตัวชี้ตามค่า แต่ Ruby ไม่มีตัวชี้ AFAIK)

class A {
  public:
    int x;
};

void inc(A arg) {
  arg.x++;
  printf("in inc: %d\n", arg.x); // => 6
}

void inc(A* arg) {
  arg->x++;
  printf("in inc: %d\n", arg->x); // => 1
}

int main() {
  A a;
  a.x = 5;
  inc(a);
  printf("in main: %d\n", a.x); // => 5

  A* b = new A;
  b->x = 0;
  inc(b);
  printf("in main: %d\n", b->x); // => 1

  return 0;
}

เอาท์พุท:

in inc: 6
in main: 5
in inc: 1
in main: 1

ใน C ++ "pass by reference" หมายถึงฟังก์ชั่นสามารถเข้าถึงตัวแปรต้นฉบับได้ มันสามารถกำหนดจำนวนเต็มตามตัวอักษรใหม่ทั้งหมดและตัวแปรเดิมจะมีค่านั้นด้วย

void replace(A &arg) {
  A newA;
  newA.x = 10;
  arg = newA;
  printf("in replace: %d\n", arg.x);
}

int main() {
  A a;
  a.x = 5;
  replace(a);
  printf("in main: %d\n", a.x);

  return 0;
}

เอาท์พุท:

in replace: 10
in main: 10

Ruby ใช้ pass by value (ในความหมาย C ++) หากอาร์กิวเมนต์ไม่ใช่วัตถุ แต่ใน Ruby ทุกอย่างเป็นวัตถุดังนั้นจึงไม่มีคุณค่าในความรู้สึกของ C ++ ใน Ruby

ใน Ruby มีการใช้ "pass by object reference" (เพื่อใช้คำศัพท์ของ Python):

  • ภายในฟังก์ชั่นสมาชิกของวัตถุสามารถกำหนดค่าใหม่ให้กับพวกเขาและการเปลี่ยนแปลงเหล่านี้จะยังคงอยู่หลังจากที่ฟังก์ชั่นกลับมา *
  • ภายในฟังก์ชันการกำหนดวัตถุใหม่ทั้งหมดให้กับตัวแปรทำให้ตัวแปรหยุดการอ้างอิงวัตถุเก่า แต่หลังจากฟังก์ชั่นกลับมาตัวแปรเดิมจะยังคงอ้างอิงวัตถุเก่า

ดังนั้น Ruby ไม่ใช้ "pass by reference" ในความหมาย C ++ ถ้าเป็นเช่นนั้นการกำหนดวัตถุใหม่ให้กับตัวแปรภายในฟังก์ชั่นจะทำให้วัตถุเก่าถูกลืมหลังจากฟังก์ชันส่งคืน

class A
  attr_accessor :x
end

def inc(arg)
  arg.x += 1
  puts arg.x
end

def replace(arg)
  arg = A.new
  arg.x = 3
  puts arg.x
end

a = A.new
a.x = 1
puts a.x  # 1

inc a     # 2
puts a.x  # 2

replace a # 3
puts a.x  # 2

puts ''

def inc_var(arg)
  arg += 1
  puts arg
end

b = 1     # Even integers are objects in Ruby
puts b    # 1
inc_var b # 2
puts b    # 1

เอาท์พุท:

1
2
2
3
2

1
2
1

* นี่คือเหตุผลว่าทำไมใน Ruby หากคุณต้องการแก้ไขวัตถุภายในฟังก์ชั่น แต่ลืมการเปลี่ยนแปลงเหล่านั้นเมื่อฟังก์ชั่นส่งคืนดังนั้นคุณต้องทำสำเนาของวัตถุอย่างชัดเจนก่อนทำการเปลี่ยนแปลงสำเนาชั่วคราว


คำตอบของคุณดีที่สุด ฉันอยากจะโพสต์ตัวอย่างง่ายๆ def ch(str) str.reverse! end; str="abc"; ch(str); puts str #=> "cba"
fangxing

นี่คือคำตอบที่ถูกต้อง! และนี่ก็เป็นอย่างดีอธิบายที่นี่: robertheaton.com/2014/07/22/... def foo(bar) bar = 'reference' end; baz = 'value'; foo(baz); puts "Ruby is pass-by-#{baz}"แต่สิ่งที่ผมยังไม่เข้าใจคือ: สิ่งนี้พิมพ์ "Ruby is pass-by-value" แต่ตัวแปรภายในfooถูกกำหนดใหม่ ถ้าจะเป็นอาร์เรย์กำหนดใหม่จะไม่ได้ผลbar bazทำไม?
haffla

ฉันไม่เข้าใจคำถามของคุณ ฉันคิดว่าคุณควรถามคำถามใหม่ทั้งหมดแทนที่จะถามในความคิดเห็นที่นี่
David Winiecki

42

Ruby ผ่านการอ้างอิงหรือตามค่าหรือไม่

Ruby คือรหัสผ่าน เสมอ. ไม่มีข้อยกเว้น. ไม่เลย ไม่เป็นไร

นี่เป็นโปรแกรมง่ายๆที่แสดงให้เห็นถึงความจริงที่ว่า:

def foo(bar)
  bar = 'reference'
end

baz = 'value'

foo(baz)

puts "Ruby is pass-by-#{baz}"
# Ruby is pass-by-value

15
@DavidJ .: "ความผิดพลาดที่นี่เป็นที่พารามิเตอร์ท้องถิ่นเป็นพระราชเสาวนีย์ (ชี้ไปที่สถานที่ใหม่ในหน่วยความจำ)" - ที่ไม่เป็นความผิดพลาดที่เป็นความหมายของการส่งผ่านโดยค่า ถ้า Ruby เป็นแบบอ้างอิงอ้างอิงการโอนสิทธิในการเชื่อมต่อกับวิธีการโลคอลอาร์กิวเมนต์ใหม่ใน callee ก็จะได้รับการกำหนดตัวแปรโลคอลในผู้เรียกอีกครั้ง ซึ่งมันไม่ได้ Ergo, Ruby เป็นรหัสผ่าน ความจริงที่ว่าถ้าคุณเปลี่ยนค่าที่ไม่แน่นอนค่าการเปลี่ยนแปลงจะไม่เกี่ยวข้องอย่างสมบูรณ์นั่นเป็นเพียงการทำงานของสถานะที่ไม่แน่นอน Ruby ไม่ใช่ภาษาที่ใช้งานได้จริง
Jörg W Mittag

5
ขอบคุณJörgเพื่อปกป้องคำจำกัดความที่แท้จริงของ "pass-by-value" เห็นได้ชัดว่ามันละลายสมองของเราเมื่อคุณค่าคือการอ้างอิงแม้ว่าทับทิมจะผ่านค่าเสมอ
Douglas

9
นี่คือความซับซ้อน ความแตกต่างในทางปฏิบัติระหว่าง "ผ่านค่า" และ "ผ่านอ้างอิง" เป็นความหมายไม่ใช่ syntax คุณจะบอกว่าอาร์เรย์ C มีค่าผ่านหรือไม่ ไม่แน่นอนแม้ว่าเมื่อคุณส่งชื่อของอาร์เรย์ไปยังฟังก์ชันที่คุณกำลังส่งผ่านตัวชี้ที่ไม่เปลี่ยนรูปและข้อมูลที่ตัวชี้อ้างถึงสามารถกลายพันธุ์ได้เท่านั้น ประเภทของค่าที่ชัดเจนใน Ruby ถูกส่งผ่านโดยค่าและประเภทการอ้างอิงจะถูกส่งผ่านโดยการอ้างอิง
dodgethesteamroller

3
@dodgethesteamroller: ทั้ง Ruby และ C เป็นรหัสผ่าน เสมอ. ไม่มีข้อยกเว้นไม่ใช่ถ้าไม่ใช่ แต่ไม่ใช่ ความแตกต่างระหว่าง pass-by-value และ pass-by-reference ไม่ว่าคุณจะผ่านค่าจุดอ้างอิงไปหรือผ่านการอ้างอิง C ส่งค่าเสมอไม่อ้างอิง ค่าอาจเป็นหรือไม่ใช่ตัวชี้ แต่สิ่งที่มีค่านั้นไม่เกี่ยวข้องกับว่ามันถูกส่งผ่านในครั้งแรก Ruby ยังส่งค่าเสมอไม่อ้างอิง ค่านั้นเป็นตัวชี้เสมอแต่อีกครั้งนั้นไม่เกี่ยวข้อง
Jörg W Mittag

44
คำตอบนี้ในขณะที่พูดอย่างเคร่งครัดจริง , ไม่ได้เป็นอย่างที่มีประโยชน์ ความจริงที่ว่าค่าที่ส่งผ่านนั้นเป็นตัวชี้เสมอไม่เกี่ยวข้อง มันเป็นแหล่งของความสับสนสำหรับผู้ที่พยายามเรียนรู้และคำตอบของคุณไม่ได้ช่วยอะไรให้สับสนอย่างแน่นอน

20

Ruby เป็นค่าทีละค่าในความหมายที่เข้มงวด แต่ค่าเหล่านั้นเป็นการอ้างอิง

สิ่งนี้อาจเรียกว่า " pass-reference-by-value " บทความนี้มีคำอธิบายที่ดีที่สุดที่ฉันได้อ่าน: http://robertheaton.com/2014/07/22/is-ruby-pass-by-reference-or-pass-by-value/

รหัสผ่านอ้างอิงโดยค่าสามารถอธิบายสั้น ๆ ดังนี้

ฟังก์ชั่นได้รับการอ้างอิงถึง (และจะเข้าถึง) วัตถุเดียวกันในหน่วยความจำที่ใช้โดยผู้โทร อย่างไรก็ตามจะไม่ได้รับกล่องที่ผู้โทรกำลังจัดเก็บวัตถุนี้ใน; เช่นเดียวกับใน pass-value-by-value ฟังก์ชั่นให้กล่องของตัวเองและสร้างตัวแปรใหม่สำหรับตัวเอง

พฤติกรรมที่เกิดขึ้นจริง ๆ แล้วเป็นการรวมกันของคำนิยามแบบคลาสสิกของการอ้างอิงผ่านและการส่งต่อตามค่า


"การอ้างอิงผ่านตามค่า" เป็นวลีเดียวกับที่ฉันใช้เพื่ออธิบายการทะเลาะกันของรูบี้ ฉันคิดว่ามันเป็นวลีที่แม่นยำและกระชับที่สุด
Wayne Conrad

16

มีคำตอบที่ดีอยู่แล้ว แต่ฉันต้องการโพสต์คำจำกัดความของคู่ของเจ้าหน้าที่ในเรื่องนี้ แต่หวังว่าจะมีใครซักคนอธิบายว่าเจ้าหน้าที่ Matz (ผู้สร้างทับทิม) กล่าวว่าอย่างไรและ David Flanagan มีความหมายในหนังสือ O'Reilly ยอดเยี่ยมทับทิมเขียนโปรแกรมภาษา

[จาก 3.8.1: การอ้างอิงวัตถุ]

เมื่อคุณส่งวัตถุไปยังวิธีการใน Ruby มันเป็นการอ้างอิงวัตถุที่ถูกส่งผ่านไปยังวิธีการ มันไม่ใช่วัตถุเองและไม่ใช่การอ้างอิงถึงการอ้างอิงไปยังวัตถุ อีกวิธีหนึ่งในการพูดแบบนี้ก็คือการโต้แย้งวิธีการจะถูกส่งผ่านโดยค่ามากกว่าโดยการอ้างอิงแต่ค่าที่ส่งผ่านเป็นการอ้างอิงวัตถุ

เนื่องจากการอ้างอิงวัตถุถูกส่งผ่านไปยังเมธอดเมธอดสามารถใช้การอ้างอิงเหล่านั้นเพื่อปรับเปลี่ยนวัตถุต้นแบบ การปรับเปลี่ยนเหล่านี้จะปรากฏให้เห็นเมื่อเมธอดส่งคืน

ทั้งหมดนี้สมเหตุสมผลสำหรับฉันจนถึงย่อหน้าสุดท้ายและโดยเฉพาะอย่างยิ่งประโยคสุดท้าย นี่เป็นสิ่งที่ทำให้เข้าใจผิดได้ดีที่สุดและแย่กว่าเดิม ในทางใดทางหนึ่งการปรับเปลี่ยนการอ้างอิงแบบส่งต่อค่าจะเปลี่ยนวัตถุต้นแบบได้อย่างไร


1
เนื่องจากการอ้างอิงไม่ได้รับการแก้ไข วัตถุพื้นฐานคือ
dodgethesteamroller

1
เพราะวัตถุไม่แน่นอน Ruby ไม่ใช่ภาษาที่ใช้งานได้จริง นี่คือ orthogonal ทั้งหมดในการส่งผ่านโดยอ้างอิงเทียบกับ pass-by-value (ยกเว้นความจริงที่ว่าในภาษาที่ใช้งานได้จริง ๆ , pass-by-value และ pass-by-reference มักให้ผลลัพธ์เดียวกันดังนั้นภาษาจึงสามารถ ใช้อย่างใดอย่างหนึ่งหรือทั้งสองโดยที่คุณไม่เคยรู้)
Jörg W Mittag

ตัวอย่างที่ดีคือถ้าแทนที่จะมอบหมายตัวแปรในฟังก์ชั่นคุณดูกรณีของการส่งแฮชไปยังฟังก์ชั่นและทำการผสาน! เมื่อแฮชผ่าน แฮชดั้งเดิมจะถูกดัดแปลง
elc

16

Ruby ผ่านการอ้างอิงหรือตามค่าหรือไม่

Ruby เป็นข้อมูลอ้างอิง เสมอ. ไม่มีข้อยกเว้น. ไม่เลย ไม่เป็นไร

นี่เป็นโปรแกรมง่ายๆที่แสดงให้เห็นถึงความจริงที่ว่า:

def foo(bar)
  bar.object_id
end

baz = 'value'

puts "#{baz.object_id} Ruby is pass-by-reference #{foo(baz)} because object_id's (memory addresses) are always the same ;)"

=> 2279146940 Ruby เป็นแบบอ้างอิงอ้างอิง 2279146940 เนื่องจาก object_id (ที่อยู่หน่วยความจำ) เหมือนกันเสมอ)

def bar(babar)
  babar.replace("reference")
end

bar(baz)

puts "some people don't realize it's reference because local assignment can take precedence, but it's clearly pass-by-#{baz}"

=> บางคนไม่ทราบว่าเป็นข้อมูลอ้างอิงเพราะการมอบหมายงานในท้องถิ่นอาจมีความสำคัญมากกว่า


นี่เป็นคำตอบเดียวที่ถูกต้องและนำเสนอ gotchas ที่ดี: ลอง a = 'foobar'; b = a; b [5] = 'z' ทั้ง a และ b จะได้รับการแก้ไข
Martijn

2
@Martijn: อาร์กิวเมนต์ของคุณไม่ถูกต้องทั้งหมด มาดูคำสั่ง code ของคุณตาม statement a = 'foobar' สร้างการอ้างอิงใหม่ที่ชี้ไปที่ 'foobar' b = a สร้างการอ้างอิงที่สองไปยังข้อมูลเดียวกันกับ a b [5] = 'z' เปลี่ยนอักขระตัวที่หกของค่าที่อ้างอิงโดย b เป็น 'z' (ค่าที่อ้างอิงโดยบังเอิญยังได้รับการเปลี่ยนแปลงด้วย) นั่นเป็นเหตุผลที่ "ทั้งคู่ได้รับการแก้ไข" ในคำของคุณหรือมากกว่านั้นอย่างแม่นยำทำไม "ค่าที่อ้างอิงโดยตัวแปรทั้งสองได้รับการแก้ไข"
Lukas_Skywalker

2
คุณไม่ได้ทำอะไรกับการอ้างอิงในbarวิธีการของคุณ คุณเพียงแค่ปรับเปลี่ยนวัตถุที่การอ้างอิงชี้ไป แต่ไม่ใช่การอ้างอิงเอง วิธีเดียวที่จะแก้ไขการอ้างอิงใน Ruby คือการมอบหมาย คุณไม่สามารถแก้ไขการอ้างอิงโดยวิธีการเรียกใช้ใน Ruby เนื่องจากเมธอดสามารถเรียกใช้บนอ็อบเจ็กต์และการอ้างอิงไม่ใช่วัตถุใน Ruby ตัวอย่างโค้ดของคุณแสดงให้เห็นว่า Ruby ได้แชร์สถานะที่ไม่แน่นอน (ซึ่งไม่ได้อยู่ภายใต้การสนทนาที่นี่) แต่ก็ไม่มีอะไรจะแสดงความแตกต่างระหว่างการส่งต่อค่าและการอ้างอิงผ่าน
Jörg W Mittag

1
เมื่อมีคนถามว่าภาษานั้นเป็น "การอ้างอิงโดยใช้" พวกเขามักต้องการที่จะรู้ว่าเมื่อคุณส่งบางสิ่งไปยังฟังก์ชั่นและฟังก์ชั่นแก้ไขหรือไม่มันจะถูกแก้ไขนอกฟังก์ชั่นหรือไม่ สำหรับทับทิมคำตอบคือ 'ใช่' คำตอบนี้มีประโยชน์ในการแสดงให้เห็นว่าคำตอบของ @ JörgWMittagนั้นไม่มีประโยชน์อย่างมาก
Toby 1 Kenobi

@ Toby1Kenobi: คุณมีอิสระที่จะใช้คำจำกัดความส่วนบุคคลของคุณเองสำหรับคำว่า "pass-by-value" ซึ่งแตกต่างจากคำนิยามทั่วไปที่ใช้กันอย่างแพร่หลาย อย่างไรก็ตามถ้าคุณทำเช่นนั้นคุณควรเตรียมพร้อมสำหรับคนที่จะสับสนโดยเฉพาะอย่างยิ่งถ้าคุณละเลยที่จะเปิดเผยความจริงที่ว่าคุณกำลังพูดถึงเรื่องที่แตกต่างกันมากในบางแง่มุมแม้ความคิดตรงกันข้ามกับคนอื่น โดยเฉพาะอย่างยิ่ง "Pass-by-Reference" ไม่เกี่ยวข้องกับว่า "บางสิ่ง" ที่ส่งผ่านสามารถแก้ไขได้หรือไม่ แต่ด้วยสิ่งที่ "บางสิ่ง" นั้นโดยเฉพาะไม่ว่าจะเป็นการอ้างอิง ...
Jörg W Mittag

8

พารามิเตอร์เป็นสำเนาของการอ้างอิงดั้งเดิม ดังนั้นคุณสามารถเปลี่ยนค่าได้ แต่ไม่สามารถเปลี่ยนการอ้างอิงดั้งเดิมได้


2

ลองนี้: -

1.object_id
#=> 3

2.object_id
#=> 5

a = 1
#=> 1
a.object_id
#=> 3

b = 2
#=> 2
b.object_id
#=> 5

identifier a มี object_id 3 สำหรับ value object 1 และ identifier b มี object_id 5 สำหรับ value object 2

ตอนนี้ทำสิ่งนี้: -

a.object_id = 5
#=> error

a = b
#value(object_id) at b copies itself as value(object_id) at a. value object 2 has object_id 5
#=> 2

a.object_id 
#=> 5

ตอนนี้ a และ b ทั้งคู่มี object_id 5 เดียวกันซึ่งหมายถึง object value 2 ดังนั้นตัวแปร Ruby ประกอบด้วย object_ids เพื่ออ้างถึง object value

การทำดังต่อไปนี้ยังให้ข้อผิดพลาด: -

c
#=> error

แต่การทำเช่นนี้จะไม่ทำให้เกิดข้อผิดพลาด: -

5.object_id
#=> 11

c = 5
#=> value object 5 provides return type for variable c and saves 5.object_id i.e. 11 at c
#=> 5
c.object_id
#=> 11 

a = c.object_id
#=> object_id of c as a value object changes value at a
#=> 11
11.object_id
#=> 23
a.object_id == 11.object_id
#=> true

a
#=> Value at a
#=> 11

นี่คือตัวระบุวัตถุส่งกลับค่า 11 ซึ่งมี ID วัตถุคือ 23 เช่น object_id 23 เป็นตัวระบุ a ตอนนี้เราเห็นตัวอย่างโดยใช้วิธี

def foo(arg)
  p arg
  p arg.object_id
end
#=> nil
11.object_id
#=> 23
x = 11
#=> 11
x.object_id
#=> 23
foo(x)
#=> 11
#=> 23

arg in foo ถูกกำหนดด้วยค่าส่งคืนของ x มันแสดงให้เห็นอย่างชัดเจนว่าอาร์กิวเมนต์ถูกส่งผ่านตามค่า 11 และค่า 11 เป็นวัตถุที่มี ID วัตถุที่ไม่ซ้ำกัน 23

ดูสิ่งนี้ด้วย: -

def foo(arg)
  p arg
  p arg.object_id
  arg = 12
  p arg
  p arg.object_id
end

#=> nil
11.object_id
#=> 23
x = 11
#=> 11
x.object_id
#=> 23
foo(x)
#=> 11
#=> 23
#=> 12
#=> 25
x
#=> 11
x.object_id
#=> 23

ที่นี่ตัวระบุ arg แรกมี object_id 23 เพื่ออ้างถึง 11 และหลังจากการมอบหมายภายในกับวัตถุค่า 12 มันมี object_id 25 แต่จะไม่เปลี่ยนค่าอ้างอิงโดย identifier x ที่ใช้ในวิธีการโทร

ดังนั้นทับทิมจะผ่านค่าและตัวแปร Ruby ไม่มีค่า แต่มีการอ้างอิงถึงวัตถุค่า


1

ทับทิมถูกตีความ ตัวแปรอ้างอิงถึงข้อมูล แต่ไม่ใช่ข้อมูลเอง สิ่งอำนวยความสะดวกนี้ใช้ตัวแปรเดียวกันสำหรับข้อมูลประเภทต่าง ๆ

การกำหนด lhs = rhs จะคัดลอกการอ้างอิงบน rhs ไม่ใช่ข้อมูล สิ่งนี้แตกต่างกันในภาษาอื่นเช่น C ซึ่งการมอบหมายให้คัดลอกข้อมูลไปยัง lhs จาก rhs

ดังนั้นสำหรับการเรียกใช้ฟังก์ชันตัวแปรที่ส่งผ่านจะบอกว่า x ถูกคัดลอกไปยังตัวแปรท้องถิ่นในฟังก์ชัน แต่ x เป็นการอ้างอิง จากนั้นจะมีสำเนาของการอ้างอิงสองชุดทั้งที่อ้างอิงถึงข้อมูลเดียวกัน หนึ่งจะอยู่ในการโทรหนึ่งในฟังก์ชั่น

การกำหนดในฟังก์ชันจะคัดลอกการอ้างอิงใหม่ไปยังรุ่นของฟังก์ชัน หลังจากนี้เวอร์ชันของผู้โทรยังคงไม่เปลี่ยนแปลง มันยังคงเป็นการอ้างอิงถึงข้อมูลต้นฉบับ

ในทางตรงกันข้ามการใช้วิธีการ. แทนที่บน x จะทำให้ทับทิมทำสำเนาข้อมูล หากมีการใช้งานการแทนที่ก่อนที่จะมีการมอบหมายใหม่ผู้โทรจะเห็นการเปลี่ยนแปลงข้อมูลในเวอร์ชั่น

ในทำนองเดียวกันตราบใดที่การอ้างอิงดั้งเดิมอยู่ในชั้นเชิงสำหรับตัวแปรส่งผ่านตัวแปรอินสแตนซ์จะเหมือนกับที่ผู้เรียกเห็น ภายในกรอบของวัตถุตัวแปรอินสแตนซ์จะมีค่าอ้างอิงที่เป็นปัจจุบันมากที่สุดเสมอไม่ว่าจะมีการจัดเตรียมโดยผู้เรียกหรือตั้งค่าในฟังก์ชันที่คลาสส่งผ่านไป

'การโทรตามค่า' หรือ 'การโทรโดยการอ้างอิง' มีการสับสนที่นี่เนื่องจากความสับสนมากกว่า '=' ในภาษาที่คอมไพล์ '=' เป็นสำเนาข้อมูล ที่นี่ในภาษาที่แปล '=' นี้เป็นสำเนาอ้างอิง ในตัวอย่างคุณมีการอ้างอิงที่ส่งผ่านแล้วตามด้วยสำเนาอ้างอิงแม้ว่า '=' ที่ปิดกั้นการส่งผ่านเดิมในการอ้างอิงแล้วผู้คนพูดถึงมันราวกับว่า '=' เป็นสำเนาข้อมูล

เพื่อให้สอดคล้องกับคำจำกัดความเราจะต้องรักษาด้วย '.replace' เนื่องจากเป็นสำเนาข้อมูล จากมุมมองของ '.replace' เราจะเห็นว่านี่คือการอ้างอิงโดยแท้จริง นอกจากนี้หากเราดำเนินการผ่านดีบักเกอร์เราจะเห็นการอ้างอิงที่ส่งผ่านเนื่องจากตัวแปรเป็นการอ้างอิง

อย่างไรก็ตามหากเราต้องเก็บ '=' เป็นกรอบอ้างอิงเราจะเห็นการส่งผ่านข้อมูลจนถึงการมอบหมายแล้วเราจะไม่เห็นอีกหลังจากการมอบหมายในขณะที่ข้อมูลของผู้โทรยังคงไม่เปลี่ยนแปลง ในระดับพฤติกรรมสิ่งนี้จะถูกส่งผ่านตามค่าตราบใดที่เราไม่พิจารณาการส่งผ่านค่าเป็นคอมโพสิตเนื่องจากเราจะไม่สามารถเก็บส่วนหนึ่งไว้ได้ในขณะที่เปลี่ยนส่วนอื่น ๆ ในการมอบหมายครั้งเดียว เปลี่ยนการอ้างอิงและต้นฉบับไม่อยู่ในขอบเขต) นอกจากนี้ยังจะมีหูดในตัวแปรเช่นนั้นในวัตถุจะอ้างอิงเช่นเดียวกับตัวแปรทั้งหมด ดังนั้นเราจะถูกบังคับให้พูดคุยเกี่ยวกับการส่งผ่าน 'การอ้างอิงโดยค่า' และต้องใช้ตำแหน่งที่เกี่ยวข้อง


1

ควรสังเกตว่าคุณไม่จำเป็นต้องใช้วิธี "replace" เพื่อเปลี่ยนค่าดั้งเดิม หากคุณกำหนดหนึ่งในค่าแฮชสำหรับแฮชคุณกำลังเปลี่ยนค่าดั้งเดิม

def my_foo(a_hash)
  a_hash["test"]="reference"
end;

hash = {"test"=>"value"}
my_foo(hash)
puts "Ruby is pass-by-#{hash["test"]}"

อีกสิ่งที่ฉันพบ หากคุณผ่านประเภทตัวเลขประเภทตัวเลขทั้งหมดจะไม่เปลี่ยนรูปและทำให้ผ่านค่า ฟังก์ชั่นแทนที่ที่ทำงานกับสตริงด้านบนไม่ทำงานสำหรับประเภทตัวเลขใด ๆ
Don Carr

1
Two references refer to same object as long as there is no reassignment. 

การอัปเดตใด ๆ ในวัตถุเดียวกันจะไม่อ้างอิงหน่วยความจำใหม่เนื่องจากยังอยู่ในหน่วยความจำเดียวกัน นี่คือตัวอย่างบางส่วน:

    a = "first string"
    b = a



    b.upcase! 
    => FIRST STRING
    a
    => FIRST STRING

    b = "second string"


a
    => FIRST STRING
    hash = {first_sub_hash: {first_key: "first_value"}}
first_sub_hash = hash[:first_sub_hash]
first_sub_hash[:second_key] = "second_value"

    hash
    => {first_sub_hash: {first_key: "first_value", second_key: "second_value"}}

    def change(first_sub_hash)
    first_sub_hash[:third_key] = "third_value"
    end

    change(first_sub_hash)

    hash
    =>  {first_sub_hash: {first_key: "first_value", second_key: "second_value", third_key: "third_value"}}

0

ใช่ แต่ ....

Ruby ผ่านการอ้างอิงไปยังวัตถุและเนื่องจากทุกอย่างในทับทิมเป็นวัตถุคุณสามารถพูดได้ว่ามันผ่านการอ้างอิง

ฉันไม่เห็นด้วยกับการโพสต์ที่นี่โดยอ้างว่าผ่านไปตามมูลค่าดูเหมือนว่าจะเป็นเกมที่คลั่งไคล้

อย่างไรก็ตามผลที่ได้คือ "ซ่อน" พฤติกรรมเนื่องจากการดำเนินการทับทิมส่วนใหญ่ให้ "ออกจากกล่อง" - ตัวอย่างเช่นการดำเนินงานของสตริงสร้างสำเนาของวัตถุ:

> astringobject = "lowercase"

> bstringobject = astringobject.upcase
> # bstringobject is a new object created by String.upcase

> puts astringobject
lowercase

> puts bstringobject
LOWERCASE

ซึ่งหมายความว่าส่วนใหญ่แล้ววัตถุดั้งเดิมจะถูกทิ้งไว้โดยไม่มีการเปลี่ยนแปลงเพื่อให้ดูเหมือนว่าทับทิมเป็น "ผ่านค่า"

แน่นอนว่าเมื่อคุณออกแบบคลาสของคุณเองความเข้าใจในรายละเอียดของพฤติกรรมนี้เป็นสิ่งสำคัญสำหรับทั้งพฤติกรรมการทำงานประสิทธิภาพของหน่วยความจำและประสิทธิภาพ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.