Tại sao có nhiều ngôn ngữ được truyền theo giá trị?


36

Ngay cả các ngôn ngữ mà bạn có thao tác con trỏ rõ ràng như C, nó luôn được truyền theo giá trị (bạn có thể chuyển chúng bằng tham chiếu nhưng đó không phải là hành vi mặc định).

Lợi ích của việc này là gì, tại sao có nhiều ngôn ngữ được truyền bởi các giá trị và tại sao các ngôn ngữ khác được truyền qua tham chiếu ? (Tôi hiểu Haskell được thông qua tham chiếu mặc dù tôi không chắc chắn).


5
void acceptEntireProgrammingLanguageByValue(C++);
Thomas Eding

4
Nó có thể tồi tệ hơn. Một số ngôn ngữ cũ cũng được phép gọi theo tên
hugomg

25
Trên thực tế, trong C bạn không thể vượt qua bằng cách tham khảo. Bạn có thể truyền một con trỏ theo giá trị , rất giống với tham chiếu qua, nhưng không giống nhau. Trong C ++, mặc dù, bạn có thể vượt qua bằng cách tham khảo.
Mason Wheeler

@Mason Wheeler bạn có thể xây dựng thêm hoặc thêm một số liên kết, beacuase tuyên bố của bạn không rõ ràng với tôi vì tôi không phải là chuyên gia về C / C ++, chỉ là một lập trình viên thông thường, cảm ơn
Betlista

1
@Betlista: Với việc chuyển qua tham chiếu, bạn có thể viết một thói quen trao đổi giống như thế này: temp := a; a := b; b := temp;Và khi nó trả về, các giá trị của absẽ được hoán đổi. Không có cách nào để làm điều đó trong C; bạn phải vượt qua các con trỏ tới abswapthường xuyên có hành động theo giá trị mà họ trỏ đến.
Mason Wheeler

Câu trả lời:


58

Truyền theo giá trị thường an toàn hơn truyền qua tham chiếu, vì bạn không thể vô tình sửa đổi các tham số cho phương thức / hàm của mình. Điều này làm cho ngôn ngữ đơn giản hơn để sử dụng, vì bạn không phải lo lắng về các biến bạn đưa ra cho một hàm. Bạn biết họ sẽ không được thay đổi, và đây thường là những gì bạn mong đợi .

Tuy nhiên, nếu bạn muốn sửa đổi các tham số, bạn cần có một số thao tác rõ ràng để làm rõ điều này (truyền vào một con trỏ). Điều này sẽ buộc tất cả người gọi của bạn thực hiện cuộc gọi hơi khác ( &variabletrong C) và điều này làm cho nó rõ ràng rằng tham số biến có thể được thay đổi.

Vì vậy, bây giờ bạn có thể giả sử rằng một hàm sẽ không thay đổi tham số biến của bạn, trừ khi nó được đánh dấu rõ ràng để làm như vậy (bằng cách yêu cầu bạn chuyển qua một con trỏ). Đây là một giải pháp an toàn và sạch sẽ hơn so với giải pháp thay thế: Giả sử mọi thứ có thể thay đổi các tham số của bạn, trừ khi họ nói cụ thể là không thể.


4
+1 Mảng phân rã cho con trỏ trình bày một ngoại lệ đáng chú ý, nhưng giải thích tổng thể là tốt.
dasblinkenlight

6
@dasblinkenlight: mảng là một điểm nhức nhối trong C :(
Matthieu M.

55

Call-by-value và call-by-Reference là các kỹ thuật triển khai bị nhầm lẫn với các chế độ truyền tham số từ lâu.

Ban đầu, đã có FORTRAN. FORTRAN chỉ có tham chiếu theo cuộc gọi, vì các chương trình con phải có thể sửa đổi các tham số của chúng và các chu kỳ tính toán quá tốn kém để cho phép nhiều chế độ truyền tham số, cộng với việc không biết về lập trình khi FORTRAN được xác định lần đầu tiên.

ALGOL đã đưa ra các cuộc gọi theo tên và gọi theo giá trị. Gọi theo giá trị là cho những thứ không được thay đổi (tham số đầu vào). Gọi bằng tên là cho các tham số đầu ra. Call-by-name hóa ra là một crock lớn và ALGOL 68 đã bỏ nó.

PASCAL cung cấp cuộc gọi theo giá trị và cuộc gọi theo tham chiếu. Nó không cung cấp bất kỳ cách nào để lập trình viên nói với trình biên dịch rằng anh ta đang truyền một đối tượng lớn (thường là một mảng) bằng cách tham chiếu, để tránh thổi ngăn xếp tham số, nhưng không nên thay đổi đối tượng.

PASCAL đã thêm con trỏ vào từ vựng thiết kế ngôn ngữ.

C đã cung cấp cuộc gọi theo giá trị và mô phỏng cuộc gọi bằng cách tham chiếu bằng cách xác định toán tử kydge để trả về một con trỏ tới một đối tượng tùy ý trong bộ nhớ.

Các ngôn ngữ sau này sao chép C, chủ yếu là do các nhà thiết kế chưa bao giờ nhìn thấy bất cứ điều gì khác. Đây có lẽ là lý do tại sao gọi theo giá trị rất phổ biến.

C ++ đã thêm một loại bùn ở trên cùng của loại C để cung cấp lệnh gọi theo tham chiếu.

Bây giờ, là kết quả trực tiếp của cuộc gọi theo giá trị so với cuộc gọi theo tham chiếu so với cuộc gọi bằng con trỏ, C và C ++ (lập trình viên) có những cơn đau đầu khủng khiếp với con trỏ và con trỏ đến const (chỉ đọc) các đối tượng.

Ada quản lý để tránh cơn ác mộng này.

Ada không có cuộc gọi theo giá trị rõ ràng so với cuộc gọi theo tham chiếu. Thay vào đó, Ada có các tham số (có thể được đọc nhưng không được viết), ra các tham số (PHẢI được viết trước khi chúng có thể được đọc) và trong các tham số ngoài, có thể được đọc và ghi theo bất kỳ thứ tự nào. Trình biên dịch quyết định xem một tham số cụ thể được truyền theo giá trị hay bằng tham chiếu: nó trong suốt đối với người lập trình.


1
+1. Đây là câu trả lời duy nhất tôi thấy ở đây cho đến nay thực sự trả lời câu hỏi và có ý nghĩa và không sử dụng nó như một cái cớ minh bạch cho một lời ca ngợi ủng hộ FP.
Mason Wheeler

11
+1 cho "Các ngôn ngữ sau này sao chép C, chủ yếu là do các nhà thiết kế chưa bao giờ nhìn thấy bất cứ điều gì khác." Mỗi lần tôi nhìn thấy một ngôn ngữ mới với các hằng số bát phân có tiền tố 0, tôi sẽ chết một chút bên trong.
librik

4
Đây có thể là câu đầu tiên của Kinh thánh lập trình : In the beginning, there was FORTRAN.

1
Liên quan đến ADA, nếu một biến toàn cục được truyền cho một tham số vào / ra, liệu ngôn ngữ sẽ ngăn cuộc gọi lồng nhau kiểm tra hoặc sửa đổi nó? Nếu không, tôi nghĩ sẽ có sự khác biệt rõ ràng giữa các tham số vào / ra và ref. Cá nhân, tôi muốn thấy các ngôn ngữ hỗ trợ rõ ràng tất cả các kết hợp "vào / ra / vào + ra" và "theo giá trị / bằng cách giới thiệu / không quan tâm", vì vậy các lập trình viên có thể cung cấp sự rõ ràng tối đa theo ý định của họ nhưng tối ưu hóa sẽ có sự linh hoạt tối đa trong việc thực hiện [miễn là nó phù hợp với ý định của lập trình viên].
supercat

2
@Neikos Ngoài ra còn có một thực tế là 0tiền tố không phù hợp với mọi tiền tố mười cơ sở khác. Điều đó có thể hơi dễ sử dụng trong C (và hầu hết các ngôn ngữ tương thích nguồn, ví dụ C ++, Objective C), nếu bát phân là cơ sở thay thế duy nhất khi được giới thiệu, nhưng khi các ngôn ngữ hiện đại hơn sử dụng cả 0x0ngay từ đầu, nó chỉ khiến chúng trông kém nghĩ ra.
8bittree

13

Truyền qua tham chiếu cho phép các tác dụng phụ không mong muốn rất tinh vi rất khó xảy ra bên cạnh không thể theo dõi khi chúng bắt đầu gây ra hành vi ngoài ý muốn.

Truyền theo giá trị, đặc biệt final, statichoặc constcác tham số đầu vào làm cho toàn bộ lớp lỗi này biến mất.

Các ngôn ngữ bất biến thậm chí còn mang tính quyết định hơn và dễ dàng hơn để suy luận và hiểu những gì đang diễn ra và những gì được dự kiến ​​sẽ ra khỏi một chức năng.


7
Đó là một trong những điều mà bạn tìm ra sau khi cố gắng gỡ lỗi mã VB 'cổ đại' trong đó mọi thứ đều được tham chiếu như mặc định.
jfrankcarr

Có thể đó chỉ là nền tảng của tôi khác biệt, nhưng tôi không biết loại "tác dụng phụ không mong muốn rất tinh vi mà rất khó để không thể theo dõi" mà bạn đang nói đến. Tôi đã quen với Pascal, trong đó giá trị phụ là mặc định nhưng tham chiếu phụ có thể được sử dụng bằng cách đánh dấu rõ ràng tham số, (về cơ bản là cùng một mô hình với C #,) và tôi chưa bao giờ gặp phải vấn đề đó. Tôi có thể thấy nó sẽ có vấn đề như thế nào trong "VB cổ đại" trong đó by-ref là mặc định, nhưng khi by-ref là opt-in, nó khiến bạn phải suy nghĩ về nó trong khi bạn viết nó.
Mason Wheeler

4
@MasonWheeler những gì về người mới xuất hiện phía sau bạn và chỉ sao chép những gì bạn đã làm mà không hiểu nó và bắt đầu thao túng trạng thái đó một cách gián tiếp, thế giới thực xảy ra mọi lúc. Ít quyết đoán là một điều tốt. Bất biến là tốt nhất.

1
@MasonWheeler Các ví dụ bí danh đơn giản, như các bản Swaphack không sử dụng biến tạm thời, mặc dù không có khả năng là một vấn đề, cho thấy một ví dụ phức tạp hơn có thể khó gỡ lỗi như thế nào.
Đánh dấu

1
@MasonWheeler: Ví dụ kinh điển trong FORTRAN, dẫn đến câu nói "Biến sẽ không; hằng số", sẽ chuyển một hằng số dấu phẩy động như 3.0 sang một hàm sửa đổi tham số truyền vào. Đối với bất kỳ hằng số dấu phẩy động nào được truyền cho một hàm, hệ thống sẽ tạo một biến "ẩn" được khởi tạo với giá trị phù hợp và có thể được truyền cho các hàm. Nếu hàm thêm 1.0 vào tham số của nó, một tập hợp con tùy ý 3.0 trong chương trình có thể đột nhiên trở thành 4.0.
supercat

8

Tại sao có nhiều ngôn ngữ được truyền theo giá trị?

Điểm chia nhỏ các chương trình lớn thành các chương trình con nhỏ là bạn có thể suy luận về các chương trình con một cách độc lập. Pass-by-Reference phá vỡ tài sản này. (Như đã chia sẻ trạng thái có thể thay đổi.)

Ngay cả các ngôn ngữ mà bạn có thao tác con trỏ rõ ràng như C, nó luôn được truyền theo giá trị (bạn có thể chuyển chúng bằng tham chiếu nhưng đó không phải là hành vi mặc định).

Trên thực tế, C luôn luôn là giá trị truyền qua, không bao giờ chuyển qua tham chiếu. Bạn có thể lấy một địa chỉ của một cái gì đó và vượt qua địa chỉ đó, nhưng địa chỉ sẽ vẫn được truyền theo giá trị.

Lợi ích của việc này là gì, tại sao có nhiều ngôn ngữ được truyền bởi các giá trị và tại sao các ngôn ngữ khác được truyền qua tham chiếu ?

Có hai lý do chính để sử dụng tham chiếu qua:

  1. mô phỏng nhiều giá trị trả về
  2. hiệu quả

Cá nhân, tôi nghĩ # 1 là không có thật: hầu như luôn luôn là một cái cớ cho API và / hoặc thiết kế ngôn ngữ xấu:

  1. Nếu bạn cần nhiều giá trị trả về, đừng mô phỏng chúng, chỉ cần sử dụng ngôn ngữ hỗ trợ chúng.
  2. Bạn cũng có thể mô phỏng nhiều giá trị trả về bằng cách đóng gói chúng vào một số cấu trúc dữ liệu nhẹ như bộ dữ liệu. Điều này đặc biệt hiệu quả nếu ngôn ngữ hỗ trợ khớp mẫu hoặc phá hủy liên kết. Ví dụ: Ruby:

    def foo
      # This is actually just a single return value, an array: [1, 2, 3]
      return 1, 2, 3
    end
    
    # Ruby supports destructuring bind for arrays: a, b, c = [1, 2, 3]
    one, two, three = foo
    
  3. Thông thường, bạn thậm chí không cần nhiều giá trị trả về. Ví dụ, một mẫu phổ biến là chương trình con trả về mã lỗi và kết quả thực tế được ghi lại bằng tham chiếu. Thay vào đó, bạn chỉ nên ném một ngoại lệ nếu lỗi không mong muốn hoặc trả lại Either<Exception, T>nếu lỗi được dự kiến. Một mô hình khác là trả về một boolean cho biết thao tác có thành công hay không và trả về kết quả thực tế bằng cách tham chiếu. Một lần nữa, nếu thất bại là bất ngờ, thay vào đó, bạn nên đưa ra một ngoại lệ, nếu thất bại được dự kiến, ví dụ như khi tìm kiếm một giá trị trong từ điển, bạn nên trả lại Maybe<T>thay thế.

Pass-by-Reference có thể hiệu quả hơn pass-by-value, vì bạn không phải sao chép giá trị.

(Tôi hiểu Haskell được thông qua tham chiếu mặc dù tôi không chắc chắn).

Không, Haskell không phải là tài liệu tham khảo. Nó cũng không phải là giá trị vượt qua. Pass-by-Reference và pass-by-value đều là những chiến lược đánh giá nghiêm ngặt, nhưng Haskell không nghiêm ngặt.

Trên thực tế, Đặc tả Haskell không chỉ định bất kỳ chiến lược đánh giá cụ thể nào . Hầu hết các triển khai Hakell sử dụng hỗn hợp gọi theo tên và gọi theo nhu cầu (một biến thể của gọi bằng tên với ghi nhớ), nhưng tiêu chuẩn không bắt buộc điều này.

Lưu ý rằng ngay cả đối với các ngôn ngữ nghiêm ngặt, sẽ không có ý nghĩa để phân biệt giữa tham chiếu qua hoặc tham chiếu giá trị cho các ngôn ngữ chức năng vì sự khác biệt giữa chúng chỉ có thể được quan sát nếu bạn thay đổi tham chiếu. Do đó, người thực hiện có thể tự do lựa chọn giữa hai người, mà không phá vỡ ngữ nghĩa của ngôn ngữ.


9
"Nếu bạn cần nhiều giá trị trả về, đừng mô phỏng chúng, chỉ cần sử dụng ngôn ngữ hỗ trợ chúng." Đây là một chút của một điều kỳ lạ để nói. Về cơ bản, nó nói "nếu ngôn ngữ của bạn có thể làm mọi thứ bạn cần, nhưng một tính năng mà bạn có thể sẽ cần ít hơn 1% mã của bạn - nhưng bạn vẫn sẽ cần nó cho 1% đó - không thể thực hiện được một cách đặc biệt sạch sẽ, thì ngôn ngữ của bạn không đủ tốt cho dự án của bạn và bạn nên viết lại toàn bộ bằng ngôn ngữ khác. " Xin lỗi, nhưng đó chỉ là vô lý.
Mason Wheeler

+1 Tôi hoàn toàn đồng ý với điểm này Điểm chia nhỏ các chương trình lớn thành các chương trình con nhỏ là bạn có thể suy luận về các chương trình con một cách độc lập. Pass-by-Reference phá vỡ tài sản này. (Như đã chia sẻ trạng thái có thể thay đổi.)
Rémi

3

Có hành vi khác nhau tùy thuộc vào mô hình gọi của ngôn ngữ, loại đối số và mô hình bộ nhớ của ngôn ngữ.

Với các kiểu gốc đơn giản, truyền theo giá trị cho phép bạn truyền giá trị bằng các thanh ghi. Điều đó có thể rất nhanh vì giá trị không cần phải được tải từ bộ nhớ cũng không lưu lại. Tối ưu hóa tương tự cũng có thể chỉ đơn giản bằng cách sử dụng lại bộ nhớ được sử dụng bởi đối số một khi callee được thực hiện với nó, mà không có nguy cơ gây rối với bản sao người gọi của đối tượng. Nếu đối số là một đối tượng tạm thời, có lẽ bạn đã lưu một bản sao đầy đủ (C ++ 11 làm cho loại tối ưu hóa này thậm chí còn rõ ràng hơn với tham chiếu đúng mới và di chuyển ngữ nghĩa của nó).

Trong rất nhiều ngôn ngữ OO (C ++ là một ngoại lệ trong ngữ cảnh này), bạn không thể truyền một đối tượng theo giá trị. Bạn buộc phải vượt qua nó bằng cách tham khảo. Điều này làm cho mã đa hình theo mặc định và có tính nội tuyến hơn với khái niệm các thể hiện phù hợp với OO. Ngoài ra, nếu bạn muốn vượt qua giá trị, bạn phải tự tạo một bản sao, thừa nhận chi phí hiệu suất đã tạo ra hành động đó. Trong trường hợp này, ngôn ngữ chọn cho bạn phương pháp có nhiều khả năng mang lại cho bạn hiệu suất tốt nhất.

Trong trường hợp ngôn ngữ chức năng, tôi đoán chuyển qua giá trị hoặc tham chiếu chỉ đơn giản là một câu hỏi về tối ưu hóa. Vì các chức năng trong ngôn ngữ như vậy là thuần túy và không có tác dụng phụ, nên thực sự không có lý do gì để sao chép giá trị ngoại trừ tốc độ. Tôi thậm chí khá chắc chắn rằng ngôn ngữ như vậy thường chia sẻ cùng một bản sao của các đối tượng có cùng giá trị, một khả năng chỉ khả dụng nếu bạn sử dụng một ngữ nghĩa tham chiếu qua (const). Python cũng đã sử dụng thủ thuật này cho các chuỗi số nguyên và phổ biến (như các phương thức và tên lớp), giải thích tại sao số nguyên và chuỗi là các đối tượng không đổi trong Python. Điều này cũng giúp tối ưu hóa mã một lần nữa, bằng cách cho phép ví dụ comparaison thay vì comparaison nội dung và thực hiện đánh giá lười biếng một số dữ liệu nội bộ.


2

Bạn có thể chuyển một biểu thức theo giá trị, đó là điều tự nhiên. Chuyển biểu thức bằng tham chiếu (tạm thời) là ... lạ.


1
Ngoài ra, chuyển biểu thức bằng tham chiếu tạm thời có thể dẫn đến các lỗi xấu (bằng ngôn ngữ trạng thái), khi bạn vui vẻ thay đổi nó (vì nó chỉ là tạm thời), nhưng sau đó nó phản tác dụng khi bạn thực sự vượt qua một biến và bạn phải nghĩ ra cách giải quyết xấu xí như vượt qua foo +0 thay vì foo.
herby

2

Nếu bạn vượt qua bằng tham chiếu, thì bạn luôn luôn làm việc hiệu quả với các giá trị toàn cầu, với tất cả các vấn đề của toàn cầu (cụ thể là phạm vi và tác dụng phụ ngoài ý muốn).

Tài liệu tham khảo, giống như toàn cầu, đôi khi có lợi, nhưng chúng không nên là lựa chọn đầu tiên của bạn.


3
Tôi giả sử bạn có nghĩa là vượt qua bằng cách tham khảo trong câu đầu tiên?
jk.
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.