Là Ruby vượt qua bởi tham chiếu hoặc theo giá trị?


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đối tượng thêm lỗi vào lang_errorsbiến trong update_lanugagesphương thức. Khi tôi thực hiện lưu trên @userđối tượng, tôi mất các lỗi được lưu trữ ban đầu trong lang_errorsbiến.

Mặc dù những gì tôi đang cố gắng làm sẽ là một vụ hack (dường như không hoạt động). Tôi muốn hiểu tại sao các giá trị biến được rửa sạch. Tôi hiểu thông qua tham chiếu vì vậy tôi muốn biết làm thế nào giá trị có thể được giữ trong biến đó mà không bị xóa sạch.


Tôi cũng nhận thấy rằng tôi có thể giữ lại giá trị đó trong một đối tượng được nhân bản
Sid

1
Bạn nên nhìn vào câu trả lời của Abe Voelker. Nhưng sau khi chạy xung quanh khối này, đây là cách tôi sẽ nói. Khi bạn truyền một đối tượng Foo cho một thủ tục, một bản sao của tham chiếu đến đối tượng được truyền, thanh, Truyền theo giá trị. bạn không thể thay đổi đối tượng mà Foo trỏ tới, nhưng bạn có thể thay đổi nội dung của đối tượng mà nó trỏ tới. Vì vậy, nếu bạn vượt qua một mảng, nội dung của mảng có thể được thay đổi, nhưng bạn không thể thay đổi mảng nào đang được tham chiếu. Rất vui khi có thể sử dụng các phương pháp của Foo mà không phải lo lắng về việc làm rối tung các phụ thuộc khác vào Foo.
bobbdelsol

Câu trả lời:


244

Theo thuật ngữ truyền thống, Ruby hoàn toàn vượt qua giá trị . Nhưng đó không thực sự là những gì bạn đang hỏi ở đây.

Ruby không có bất kỳ khái niệm nào về giá trị thuần túy, không tham chiếu, vì vậy bạn chắc chắn không thể truyền một phương thức. Các biến luôn là các tham chiếu đến các đối tượng. Để có được một đối tượng sẽ không thay đổi từ bên dưới bạn, bạn cần sao chép hoặc sao chép đối tượng bạn đã vượt qua, do đó đưa ra một đối tượng mà không ai khác có tham chiếu đến. (Mặc dù điều này không chống đạn, mặc dù vậy - cả hai phương pháp nhân bản tiêu chuẩn đều sao chép nông, vì vậy các biến thể hiện của bản sao vẫn trỏ đến cùng các đối tượng mà bản gốc đã làm. Nếu các đối tượng được tham chiếu bởi ngà biến đổi, điều đó sẽ vẫn hiển thị trong bản sao, vì nó tham chiếu cùng các đối tượng.)


88
Ruby là giá trị truyền qua . Không có ifs. Không có buts. Không có ngoại lệ. Nếu bạn muốn biết liệu Ruby (hoặc bất kỳ ngôn ngữ nào khác) là tham chiếu qua hoặc giá trị truyền qua , chỉ cần dùng thử : def foo(bar) bar = 'reference' end; baz = 'value'; foo(baz); puts "Ruby is pass-by-#{baz}".
Jörg W Mittag

95
@ JörgWMittag: Vâng, nhưng sự nhầm lẫn của OP thực sự không phải là thông qua hoặc giá trị tham chiếu theo nghĩa CS nghiêm ngặt của các từ. Những gì anh ấy đã thiếu là "giá trị" bạn đang vượt qua tài liệu tham khảo. Tôi cảm thấy rằng chỉ cần nói "Đó là giá trị vượt qua" sẽ mang tính mô phạm và làm cho OP trở thành một kẻ bất đồng, vì đó thực sự không phải là ý anh ta. Nhưng cảm ơn vì đã làm rõ, vì nó quan trọng đối với những độc giả tương lai và tôi nên đưa nó vào. (Tôi luôn bị giằng xé giữa bao gồm nhiều thông tin hơn và không gây nhầm lẫn cho mọi người.)
Chuck

16
Không đồng ý với @Jorg. Ruby được thông qua tham chiếu, anh ta chỉ thay đổi tham chiếu. Thay vào đó, hãy thử: def foo (bar) bar.replace 'Reference' end; baz = 'giá trị'; foo (baz); đặt "Ruby là pass-by - # {baz}"
pguardiario

15
@pguardiario: Tôi nghĩ đó thực sự chỉ là một câu hỏi về định nghĩa. Bạn đang sử dụng định nghĩa "tham chiếu qua" mà cá nhân bạn đã đưa ra, trong khi Jörg đang sử dụng định nghĩa khoa học máy tính truyền thống. Tất nhiên, không phải việc của tôi là nói cho bạn biết cách sử dụng từ ngữ - tôi chỉ nghĩ rằng điều quan trọng là phải giải thích thuật ngữ thông thường có nghĩa là gì. Theo thuật ngữ truyền thống, Ruby là giá trị truyền qua, nhưng bản thân các giá trị là tham chiếu. Tôi hoàn toàn hiểu lý do tại sao bạn và OP thích nghĩ về điều này như một tài liệu tham khảo - nó không phải là ý nghĩa truyền thống của thuật ngữ này.
Chuck

7
Mọi thứ trong Ruby đều là một đối tượng, vì vậy Ruby không truyền qua giá trị cũng không chuyển qua tham chiếu, ít nhất là theo nghĩa các thuật ngữ đó được sử dụng trong C ++. "Chuyển qua tham chiếu đối tượng" có thể là một cách tốt hơn để mô tả những gì Ruby làm. Tuy nhiên, cuối cùng, đặt cược tốt nhất có thể là không đặt quá nhiều ý nghĩa vào bất kỳ điều khoản nào trong số này, và chỉ cần hiểu rõ về hành vi thực sự xảy ra.
David Winiecki

424

Những người trả lời khác đều đúng, nhưng một người bạn đã yêu cầu tôi giải thích điều này cho anh ta và điều thực sự sôi nổi là cách Ruby xử lý các biến, vì vậy tôi nghĩ rằng tôi sẽ chia sẻ một số hình ảnh / giải thích đơn giản mà tôi đã viết cho anh ta (xin lỗi về độ dài và có thể là một số đơn giản hóa):


Câu 1: Điều gì xảy ra khi bạn gán một biến mới strcho giá trị là 'foo'?

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

nhập mô tả hình ảnh ở đây

Trả lời: Một nhãn được gọi strđược tạo ra trỏ vào đối tượng 'foo', trạng thái của trình thông dịch Ruby này xảy ra ở vị trí bộ nhớ 2000.


Câu 2: Điều gì xảy ra khi bạn gán biến hiện có strcho một đối tượng mới bằng cách sử dụng =?

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

nhập mô tả hình ảnh ở đây

A: Nhãn strbây giờ trỏ đến một đối tượng khác.


Q3: Điều gì sẽ xảy ra khi bạn gán một biến mới =để str?

str2 = str
str2.object_id # => 2002

nhập mô tả hình ảnh ở đây

A: Một nhãn mới được gọi str2được tạo ra trỏ đến cùng một đối tượng như str.


Q4: Điều gì xảy ra nếu đối tượng được tham chiếu bởi strstr2được thay đổi?

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

nhập mô tả hình ảnh ở đây

Trả lời: Cả hai nhãn vẫn trỏ vào cùng một đối tượng, nhưng chính đối tượng đó đã bị đột biến (nội dung của nó đã thay đổi thành một thứ khác).


Làm thế nào điều này liên quan đến câu hỏi ban đầu?

Về cơ bản, nó giống như những gì xảy ra trong Q3 / Q4; phương thức lấy bản sao riêng của biến / nhãn ( str2) được truyền vào nó ( str). Nó không thể thay đổi đối tượng mà nhãn str trỏ đến , nhưng nó có thể thay đổi nội dung của đối tượng mà cả hai tham chiếu để chứa khác:

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 cũng viết blog về điều này gần đây: robertheaton.com/2014/07/22/ trên
Michael Renner

48

Ruby sử dụng "truyền tham chiếu đối tượng"

(Sử dụng thuật ngữ của Python.)

Để nói rằng Ruby sử dụng "vượt qua giá trị" hoặc "vượt qua tham chiếu" không thực sự đủ mô tả để hữu ích. Tôi nghĩ rằng hầu hết mọi người biết điều đó ngày nay, thuật ngữ đó ("giá trị" so với "tham chiếu") đến từ C ++.

Trong C ++, "truyền theo giá trị" có nghĩa là hàm lấy một bản sao của biến và mọi thay đổi đối với bản sao không thay đổi bản gốc. Điều đó cũng đúng với các đối tượng. Nếu bạn chuyển một biến đối tượng theo giá trị thì toàn bộ đối tượng (bao gồm tất cả các thành viên của nó) sẽ được sao chép và mọi thay đổi đối với các thành viên sẽ không thay đổi các thành viên đó trên đối tượng ban đầu. (Sẽ khác nếu bạn vượt qua một con trỏ theo giá trị nhưng dù sao thì Ruby cũng không có con trỏ, 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;
}

Đầu ra:

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

Trong C ++, "truyền bằng tham chiếu" có nghĩa là hàm được quyền truy cập vào biến ban đầu. Nó có thể gán một số nguyên hoàn toàn mới và biến ban đầu cũng sẽ có giá trị đó.

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;
}

Đầu ra:

in replace: 10
in main: 10

Ruby sử dụng pass by value (theo nghĩa C ++) nếu đối số không phải là đối tượng. Nhưng trong Ruby mọi thứ đều là một đối tượng, vì vậy thực sự không có giá trị nào vượt qua theo nghĩa C ++ trong Ruby.

Trong Ruby, "chuyển qua tham chiếu đối tượng" (để sử dụng thuật ngữ của Python) được sử dụng:

  • Bên trong hàm, bất kỳ thành viên nào của đối tượng đều có thể có các giá trị mới được gán cho chúng và những thay đổi này sẽ tồn tại sau khi hàm trả về. *
  • Bên trong hàm, việc gán một đối tượng hoàn toàn mới cho biến làm cho biến dừng tham chiếu đối tượng cũ. Nhưng sau khi hàm trả về, biến ban đầu vẫn sẽ tham chiếu đối tượng cũ.

Do đó, Ruby không sử dụng "pass by Reference" theo nghĩa C ++. Nếu có, việc gán một đối tượng mới cho một biến bên trong hàm sẽ khiến đối tượng cũ bị lãng quên sau khi hàm trả về.

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

Đầu ra:

1
2
2
3
2

1
2
1

* Đây là lý do tại sao, trong Ruby, nếu bạn muốn sửa đổi một đối tượng bên trong hàm mà quên những thay đổi đó khi hàm trả về, thì bạn phải tạo một bản sao rõ ràng của đối tượng trước khi thực hiện thay đổi tạm thời thành bản sao.


Câu trả lời của bạn là tốt nhất. Tôi cũng muốn đăng một ví dụ đơn giản def ch(str) str.reverse! end; str="abc"; ch(str); puts str #=> "cba"
fangxing

Đây là câu trả lời chính xác! Điều này cũng được giải thích rất tốt ở đây: robertheaton.com/2014/07/22/ . Nhưng những gì tôi vẫn không hiểu là đây : def foo(bar) bar = 'reference' end; baz = 'value'; foo(baz); puts "Ruby is pass-by-#{baz}". Bản in này "Ruby là pass-by-value". Nhưng biến bên trong foođược gán lại. Nếu barlà một mảng, việc gán lại sẽ không có hiệu lực baz. Tại sao?
haffla

Tôi không hiểu câu hỏi của bạn. Tôi nghĩ bạn nên hỏi một câu hỏi hoàn toàn mới thay vì hỏi trong các bình luận ở đây.
David Winiecki

42

Là Ruby vượt qua bởi tham chiếu hoặc theo giá trị?

Ruby là giá trị truyền qua. Luôn luôn. Không có ngoại lệ. Không có ifs. Không có buts.

Đây là một chương trình đơn giản chứng minh thực tế đó:

def foo(bar)
  bar = 'reference'
end

baz = 'value'

foo(baz)

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

15
@DavidJ.: "Lỗi ở đây là tham số cục bộ được gán lại (được chỉ đến một vị trí mới trong bộ nhớ)" - Đó không phải là một lỗi, đó là định nghĩa của giá trị truyền qua . Nếu Ruby là tham chiếu qua, thì việc gán lại cho ràng buộc đối số phương thức cục bộ trong callee cũng sẽ gán lại liên kết biến cục bộ trong trình gọi. Mà nó đã không. Ergo, Ruby là giá trị truyền qua. Thực tế là nếu bạn thay đổi một giá trị có thể thay đổi, giá trị thay đổi hoàn toàn không liên quan, đó chỉ là cách trạng thái có thể thay đổi hoạt động. Ruby không phải là một ngôn ngữ chức năng thuần túy.
Jörg W Mittag

5
Cảm ơn Jörg để bảo vệ định nghĩa thực sự của "giá trị truyền qua". Nó rõ ràng làm tan chảy bộ não của chúng ta khi giá trị thực tế là một tài liệu tham khảo, mặc dù ruby ​​luôn vượt qua giá trị.
Douglas

9
Đây là ngụy biện. Sự khác biệt thực tế giữa "vượt qua giá trị" và "vượt qua tham chiếu" là một ngữ nghĩa, không phải là một cú pháp. Bạn có nói rằng mảng C là giá trị truyền qua không? Tất nhiên là không, mặc dù khi bạn chuyển tên của một mảng cho một hàm bạn đang truyền một con trỏ bất biến và chỉ có dữ liệu mà con trỏ tham chiếu có thể bị thay đổi. Các loại giá trị rõ ràng trong Ruby được truyền theo giá trị và các loại tham chiếu được truyền bằng tham chiếu.
dodgethesteamler

3
@dodgethesteamler: Cả Ruby và C đều là giá trị truyền qua. Luôn luôn. Không có ngoại lệ, không ifs không buts. Sự khác biệt giữa pass-by-value và pass-by-Reference là việc bạn truyền giá trị mà các điểm tham chiếu đến hay vượt qua tham chiếu. C luôn vượt qua giá trị, không bao giờ tham chiếu. Giá trị có thể hoặc không thể là một con trỏ, nhưng những gì giá trị là là không thích hợp để cho dù nó đang được thông qua ở nơi đầu tiên. Ruby cũng luôn vượt qua giá trị, không bao giờ tham chiếu. Giá trị đó luôn luôn là một con trỏ, nhưng một lần nữa, điều đó không liên quan.
Jörg W Mittag

44
Câu trả lời này, trong khi nói đúng , không hữu ích lắm . Thực tế là giá trị được thông qua luôn là một con trỏ không liên quan. Đó là một nguồn gây nhầm lẫn cho những người đang cố gắng học hỏi và câu trả lời của bạn hoàn toàn không có gì để giúp với sự nhầm lẫn đó.

20

Ruby là pass-by-value theo nghĩa chặt chẽ, NHƯNG các giá trị là tham chiếu.

Điều này có thể được gọi là " vượt qua tham chiếu theo giá trị ". Bài viết này có lời giải thích tốt nhất mà tôi đã đọc: http://robertheaton.com/2014/07/22/is-ruby-pass-by-reference-or-pass-by-value/

Tham chiếu qua giá trị có thể được giải thích ngắn gọn như sau:

Một hàm nhận được một tham chiếu đến (và sẽ truy cập) cùng một đối tượng trong bộ nhớ như được người gọi sử dụng. Tuy nhiên, nó không nhận được hộp mà người gọi đang lưu trữ đối tượng này; như trong pass-value-by-value, hàm cung cấp hộp riêng và tạo một biến mới cho chính nó.

Hành vi kết quả thực sự là sự kết hợp của các định nghĩa cổ điển về truyền qua tham chiếu và truyền qua giá trị.


"truyền tham chiếu theo giá trị" là cùng một cụm từ tôi sử dụng để mô tả việc truyền đối số của Ruby. Tôi nghĩ đó là cụm từ chính xác và cô đọng nhất.
Wayne Conrad

16

Đã có một số câu trả lời tuyệt vời, nhưng tôi muốn đăng định nghĩa của một cặp chính quyền về chủ đề này, nhưng cũng hy vọng ai đó có thể giải thích những gì chính quyền Matz (người tạo ra Ruby) và David Flanagan nói trong cuốn sách O'Reilly tuyệt vời của họ, Ngôn ngữ lập trình Ruby .

[từ 3.8.1: Tài liệu tham khảo đối tượng]

Khi bạn truyền một đối tượng cho một phương thức trong Ruby, đó là một tham chiếu đối tượng được truyền cho phương thức đó. Bản thân nó không phải là đối tượng và nó không phải là một tham chiếu đến tham chiếu đến đối tượng. Một cách khác để nói điều này là các đối số phương thức được truyền bằng giá trị chứ không phải bằng tham chiếu , nhưng các giá trị được truyền là tham chiếu đối tượng.

Vì các tham chiếu đối tượng được truyền cho các phương thức, nên các phương thức có thể sử dụng các tham chiếu đó để sửa đổi đối tượng bên dưới. Những sửa đổi này sau đó được hiển thị khi phương thức trả về.

Tất cả điều này có ý nghĩa với tôi cho đến đoạn cuối cùng, và đặc biệt là câu cuối cùng. Điều này là tốt nhất gây hiểu lầm, và tại bối rối tồi tệ hơn. Bằng cách nào, bằng cách nào, những sửa đổi đối với tham chiếu truyền qua giá trị đó có thể thay đổi đối tượng cơ bản không?


1
Bởi vì tài liệu tham khảo không được sửa đổi; đối tượng cơ bản là.
dodgethesteamler

1
Bởi vì đối tượng là đột biến. Ruby không phải là một ngôn ngữ chức năng thuần túy. Điều này hoàn toàn trực giao với tham chiếu qua so với giá trị truyền qua (ngoại trừ thực tế là trong ngôn ngữ chức năng thuần túy, thông qua giá trị và tham chiếu qua luôn mang lại kết quả giống nhau, vì vậy ngôn ngữ có thể sử dụng một hoặc cả hai mà bạn không bao giờ biết).
Jörg W Mittag

Một ví dụ điển hình là nếu thay vì gán biến trong hàm, bạn xem xét trường hợp chuyển hàm băm cho hàm và thực hiện hợp nhất! trên hàm băm đã qua. Băm ban đầu kết thúc sửa đổi.
ELC

16

Là Ruby vượt qua bởi tham chiếu hoặc theo giá trị?

Ruby là tham chiếu qua. Luôn luôn. Không có ngoại lệ. Không có ifs. Không có buts.

Đây là một chương trình đơn giản chứng minh thực tế đó:

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 là tham chiếu qua 2279146940 vì object_id's (địa chỉ bộ nhớ) luôn giống nhau;)

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}"

=> một số người không nhận ra đó là tham chiếu vì việc gán cục bộ có thể được ưu tiên, nhưng rõ ràng đó là tham chiếu qua


Đây là câu trả lời đúng duy nhất và trình bày một số vấn đề hay: Hãy thử a = 'foobar'; b = a; b [5] = 'z', cả a và b sẽ được sửa đổi.
Martijn

2
@Martijn: đối số của bạn không hoàn toàn hợp lệ. Chúng ta hãy đi qua tuyên bố mã của bạn bằng tuyên bố. a = 'foobar' tạo tham chiếu mới trỏ đến 'foobar'. b = a tạo tham chiếu thứ hai cho cùng dữ liệu với a. b [5] = 'z' thay đổi ký tự thứ sáu của giá trị được tham chiếu bởi b thành 'z' (giá trị trùng hợp cũng được tham chiếu bởi một thay đổi). Đó là lý do "cả hai được sửa đổi" theo thuật ngữ của bạn, hay chính xác hơn là tại sao "giá trị được tham chiếu bởi cả hai biến được sửa đổi".
Lukas_Skywalker

2
Bạn không làm gì với tham chiếu trong barphương pháp của bạn . Bạn chỉ đơn giản là sửa đổi đối tượng mà tham chiếu trỏ đến, nhưng không phải là chính tham chiếu. Họ chỉ có cách sửa đổi các tham chiếu trong Ruby là bằng cách gán. Bạn không thể sửa đổi các tham chiếu bằng cách gọi các phương thức trong Ruby vì các phương thức chỉ có thể được gọi trên các đối tượng và các tham chiếu không phải là các đối tượng trong Ruby. Mẫu mã của bạn chứng minh rằng Ruby đã chia sẻ trạng thái có thể thay đổi (không được thảo luận ở đây), tuy nhiên không có gì để làm sáng tỏ sự khác biệt giữa tham chiếu qua giá trị và tham chiếu qua.
Jörg W Mittag

1
Khi ai đó hỏi liệu một ngôn ngữ có phải là "thông qua tham chiếu" hay không, họ thường muốn biết khi bạn chuyển một thứ gì đó cho một hàm và hàm đó sẽ sửa đổi nó, nó sẽ được sửa đổi bên ngoài hàm. Đối với Ruby câu trả lời là "có". Câu trả lời này rất hữu ích trong việc chứng minh rằng, câu trả lời của @ JörgWMittag là vô cùng hữu ích.
Toby 1 Kenobi

@ Toby1Kenobi: Tất nhiên bạn có thể tự do sử dụng định nghĩa cá nhân của riêng bạn về thuật ngữ "vượt qua giá trị" khác với định nghĩa phổ biến, được sử dụng rộng rãi. Tuy nhiên, nếu bạn làm như vậy, bạn nên chuẩn bị cho mọi người bị nhầm lẫn, đặc biệt là nếu bạn lơ là tiết lộ sự thật rằng bạn đang nói về một điều rất khác, trong một số khía cạnh thậm chí trái ngược với mọi người khác. Đặc biệt, "pass-by-reference" là không quan tâm đến việc có hay không "cái gì đó" mà được thông qua có thể được sửa đổi, nhưng thay vì với những gì mà "cái gì đó", trong đó, cho dù đó là tài liệu tham khảo ...
Jörg W Găng tay

8

Các tham số là một bản sao của tài liệu tham khảo ban đầu. Vì vậy, bạn có thể thay đổi giá trị, nhưng không thể thay đổi tham chiếu ban đầu.


2

Thử cái này:--

1.object_id
#=> 3

2.object_id
#=> 5

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

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

định danh a chứa object_id 3 cho đối tượng giá trị 1 và định danh b chứa object_id 5 cho đối tượng giá trị 2.

Bây giờ làm điều này: -

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

Bây giờ, cả a và b đều chứa cùng object_id 5, tham chiếu đến đối tượng giá trị 2. Vì vậy, biến Ruby chứa object_ids để chỉ các đối tượng giá trị.

Làm như sau cũng có lỗi: -

c
#=> error

nhưng làm điều này sẽ không gây ra lỗi: -

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

Ở đây định danh một giá trị trả về đối tượng 11 có id đối tượng là 23 tức là object_id 23 nằm ở định danh a, Bây giờ chúng ta thấy một ví dụ bằng cách sử dụng phương thức.

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 được gán với giá trị trả về của x. Nó cho thấy rõ rằng đối số được truyền bởi giá trị 11 và chính giá trị 11 là một đối tượng có id đối tượng duy nhất 23.

Bây giờ cũng thấy điều này: -

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

Ở đây, định danh arg trước tiên chứa object_id 23 để tham chiếu 11 và sau khi gán nội bộ với đối tượng giá trị 12, nó chứa object_id 25. Nhưng nó không thay đổi giá trị được tham chiếu bởi mã định danh x được sử dụng trong phương thức gọi.

Do đó, Ruby được truyền theo giá trị và các biến Ruby không chứa giá trị nhưng có chứa tham chiếu đến đối tượng giá trị.


1

Ruby được giải thích. Các biến là tham chiếu đến dữ liệu, nhưng không phải là chính dữ liệu. Điều này tạo điều kiện sử dụng cùng một biến cho dữ liệu của các loại khác nhau.

Gán lhs = rhs sau đó sao chép tham chiếu trên rhs, không phải dữ liệu. Điều này khác với các ngôn ngữ khác, chẳng hạn như C, trong đó phép gán thực hiện sao chép dữ liệu vào lhs từ rhs.

Vì vậy, đối với lời gọi hàm, biến được truyền, giả sử x, thực sự được sao chép vào một biến cục bộ trong hàm, nhưng x là tham chiếu. Sau đó sẽ có hai bản sao của tài liệu tham khảo, cả hai đều tham chiếu cùng một dữ liệu. Một người sẽ ở trong người gọi, một người trong chức năng.

Việc gán trong hàm sau đó sẽ sao chép một tham chiếu mới vào phiên bản x của hàm. Sau đó, phiên bản x của người gọi vẫn không thay đổi. Nó vẫn là một tài liệu tham khảo cho dữ liệu gốc.

Ngược lại, sử dụng phương thức .replace trên x sẽ khiến ruby ​​thực hiện sao chép dữ liệu. Nếu thay thế được sử dụng trước bất kỳ bài tập mới nào thì thực sự người gọi cũng sẽ thấy sự thay đổi dữ liệu trong phiên bản của nó.

Tương tự, miễn là tham chiếu ban đầu trong chiến thuật cho biến được truyền trong biến, các biến thể hiện sẽ giống với tham chiếu của người gọi. Trong khuôn khổ của một đối tượng, các biến đối tượng luôn có các giá trị tham chiếu cập nhật nhất, cho dù các biến đó được cung cấp bởi người gọi hay được đặt trong hàm mà lớp được truyền vào.

'Gọi theo giá trị' hoặc 'gọi theo tham chiếu' bị nhầm lẫn ở đây vì nhầm lẫn '=' Trong các ngôn ngữ được biên dịch '=' là bản sao dữ liệu. Ở đây trong ngôn ngữ được giải thích này '=' là một bản sao tham chiếu. Trong ví dụ, bạn có tham chiếu được truyền vào theo sau bởi một bản sao tham chiếu mặc dù '=' ghi lại bản gốc được truyền trong tham chiếu và sau đó mọi người nói về nó như thể '=' là một bản sao dữ liệu.

Để phù hợp với các định nghĩa, chúng ta phải tuân theo '.replace' vì đây là bản sao dữ liệu. Từ quan điểm của '.replace', chúng tôi thấy rằng điều này thực sự vượt qua tham chiếu. Hơn nữa, nếu chúng ta duyệt qua trình gỡ lỗi, chúng ta sẽ thấy các tham chiếu được truyền vào, vì các biến là các tham chiếu.

Tuy nhiên, nếu chúng ta phải giữ '=' làm khung tham chiếu, thì thực sự chúng ta sẽ thấy dữ liệu được truyền cho đến khi chuyển nhượng, và sau đó chúng ta sẽ không thấy nó nữa sau khi gán trong khi dữ liệu của người gọi không thay đổi. Ở cấp độ hành vi, giá trị này được truyền theo giá trị miễn là chúng ta không coi giá trị được chuyển thành giá trị tổng hợp - vì chúng ta sẽ không thể giữ một phần của nó trong khi thay đổi phần khác trong một nhiệm vụ duy nhất (như nhiệm vụ đó thay đổi tham chiếu và bản gốc đi ra khỏi phạm vi). Cũng sẽ có một mụn cóc, trong đó các biến thể hiện trong các đối tượng sẽ là các tham chiếu, cũng như tất cả các biến. Do đó, chúng tôi sẽ buộc phải nói về việc chuyển 'tham chiếu theo giá trị' và phải sử dụng các địa điểm liên quan.


1

Cần lưu ý rằng bạn thậm chí không phải sử dụng phương pháp "thay thế" để thay đổi giá trị ban đầu. Nếu bạn chỉ định một trong các giá trị băm cho hàm băm, bạn đang thay đổi giá trị ban đầu.

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

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

Một điều nữa tôi tìm thấy. Nếu bạn đang truyền một loại số, tất cả các loại số là bất biến, và do đó, được truyền theo giá trị. Hàm thay thế hoạt động với chuỗi ở trên, KHÔNG hoạt động đối với bất kỳ loại số nào.
Don Carr

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

Mọi cập nhật trong cùng một đối tượng sẽ không tạo tham chiếu đến bộ nhớ mới vì nó vẫn nằm trong cùng bộ nhớ. Dưới đây là một vài ví dụ:

    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

Đúng nhưng ....

Ruby chuyển một tham chiếu đến một đối tượng và vì mọi thứ trong ruby ​​là một đối tượng, nên bạn có thể nói nó chuyển qua tham chiếu.

Tôi không đồng ý với các bài đăng ở đây tuyên bố rằng nó vượt qua giá trị, nó có vẻ giống như các trò chơi mang tính mô phạm, mang tính mô phạm đối với tôi.

Tuy nhiên, trong thực tế, nó "che giấu" hành vi vì hầu hết các hoạt động mà ruby ​​cung cấp "ngoài hộp" - ví dụ như các hoạt động chuỗi, tạo ra một bản sao của đối tượng:

> astringobject = "lowercase"

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

> puts astringobject
lowercase

> puts bstringobject
LOWERCASE

Điều này có nghĩa là phần lớn thời gian, đối tượng ban đầu được giữ nguyên không thay đổi, cho vẻ ngoài của ruby ​​là "vượt qua giá trị".

Tất nhiên khi thiết kế các lớp của riêng bạn, sự hiểu biết về các chi tiết của hành vi này rất quan trọng đối với cả hành vi chức năng, hiệu quả bộ nhớ và hiệu suất.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.