Đã có câu trả lời tuyệt vời bao gồm điều này. Tôi muốn đóng góp nhỏ bằng cách chia sẻ một ví dụ rất đơn giản (sẽ biên dịch) tương phản các hành vi giữa Pass-by-Reference trong c ++ và Pass-by-value trong Java.
Một vài điểm:
- Thuật ngữ "tham chiếu" là một quá tải với hai ý nghĩa riêng biệt. Trong Java, nó chỉ đơn giản có nghĩa là một con trỏ, nhưng trong ngữ cảnh "Truyền qua tham chiếu", nó có nghĩa là một điều khiển cho biến ban đầu được truyền vào.
- Java là Pass-by-value . Java là hậu duệ của C (trong số các ngôn ngữ khác). Trước C, một số ngôn ngữ trước đây (nhưng không phải tất cả) như FORTRAN và COBOL hỗ trợ PBR, nhưng C thì không. PBR cho phép các ngôn ngữ khác thực hiện thay đổi đối với các biến được truyền trong các thường trình con. Để thực hiện điều tương tự (nghĩa là thay đổi giá trị của các biến bên trong các hàm), các lập trình viên C đã chuyển các con trỏ tới các biến thành các hàm. Các ngôn ngữ được lấy cảm hứng từ C, chẳng hạn như Java, đã mượn ý tưởng này và tiếp tục chuyển con trỏ đến các phương thức như C đã làm, ngoại trừ việc Java gọi các tham chiếu con trỏ của nó. Một lần nữa, đây là cách sử dụng khác của từ "Tham chiếu" so với "Tham chiếu qua".
- C ++ cho phép Pass-by-Reference bằng cách khai báo một tham số tham chiếu bằng cách sử dụng ký tự "&" (có thể là cùng một ký tự được sử dụng để chỉ ra "địa chỉ của một biến" trong cả C và C ++). Ví dụ: nếu chúng ta truyền vào một con trỏ bằng tham chiếu, tham số và đối số không chỉ trỏ đến cùng một đối tượng. Thay vào đó, chúng là cùng một biến. Nếu một cái được đặt thành một địa chỉ khác hoặc thành null, thì cái kia cũng vậy.
- Trong ví dụ C ++ bên dưới, tôi chuyển một con trỏ tới một chuỗi kết thúc null bằng tham chiếu . Và trong ví dụ Java bên dưới, tôi chuyển một tham chiếu Java tới một Chuỗi (một lần nữa, giống như một con trỏ tới Chuỗi) theo giá trị. Lưu ý đầu ra trong các ý kiến.
C ++ vượt qua ví dụ tham chiếu:
using namespace std;
#include <iostream>
void change (char *&str){ // the '&' makes this a reference parameter
str = NULL;
}
int main()
{
char *str = "not Null";
change(str);
cout<<"str is " << str; // ==>str is <null>
}
Java vượt qua "một tham chiếu Java" bằng ví dụ giá trị
public class ValueDemo{
public void change (String str){
str = null;
}
public static void main(String []args){
ValueDemo vd = new ValueDemo();
String str = "not null";
vd.change(str);
System.out.println("str is " + str); // ==> str is not null!!
// Note that if "str" was
// passed-by-reference, it
// WOULD BE NULL after the
// call to change().
}
}
BIÊN TẬP
Một số người đã viết bình luận dường như chỉ ra rằng họ không nhìn vào ví dụ của tôi hoặc họ không lấy ví dụ về c ++. Không chắc chắn nơi ngắt kết nối, nhưng đoán ví dụ c ++ không rõ ràng. Tôi đang đăng cùng một ví dụ trong pascal vì tôi nghĩ rằng tham chiếu qua có vẻ sạch hơn trong pascal, nhưng tôi có thể sai. Tôi có thể chỉ khiến mọi người bối rối hơn; Tôi hy vọng là không.
Trong pascal, các tham số truyền qua tham chiếu được gọi là "tham số var". Trong quy trình setToNil bên dưới, vui lòng lưu ý từ khóa 'var' có trước tham số 'ptr'. Khi một con trỏ được truyền cho thủ tục này, nó sẽ được truyền bằng tham chiếu . Lưu ý hành vi: khi quy trình này đặt ptr thành nil (pascal nói cho NULL), nó sẽ đặt đối số thành nil - bạn không thể làm điều đó trong Java.
program passByRefDemo;
type
iptr = ^integer;
var
ptr: iptr;
procedure setToNil(var ptr : iptr);
begin
ptr := nil;
end;
begin
new(ptr);
ptr^ := 10;
setToNil(ptr);
if (ptr = nil) then
writeln('ptr seems to be nil'); { ptr should be nil, so this line will run. }
end.
CHỈNH SỬA 2
Một số trích đoạn từ "Ngôn ngữ lập trình Java" của Ken Arnold, James Gosling (người đã phát minh ra Java) và David Holmes, chương 2, mục 2.6.5
Tất cả các tham số cho các phương thức được truyền "theo giá trị" . Nói cách khác, các giá trị của các biến tham số trong một phương thức là các bản sao của invoker được chỉ định làm đối số.
Anh ta tiếp tục đưa ra quan điểm tương tự về các đồ vật. . .
Bạn nên lưu ý rằng khi tham số là tham chiếu đối tượng, thì đó là tham chiếu đối tượng - không phải chính đối tượng - được truyền "theo giá trị" .
Và đến cuối cùng của phần đó, anh ta đưa ra một tuyên bố rộng hơn về java chỉ được truyền theo giá trị và không bao giờ vượt qua tham chiếu.
Ngôn ngữ lập trình Java không vượt qua các đối tượng bằng cách tham chiếu; nó
vượt qua các tham chiếu đối tượng theo giá trị . Bởi vì hai bản sao của cùng một tham chiếu tham chiếu đến cùng một đối tượng thực tế, những thay đổi được thực hiện thông qua một biến tham chiếu được hiển thị thông qua biến khác. Có chính xác một tham số chuyển chế độ - truyền theo giá trị - và giúp mọi thứ đơn giản.
Phần này của cuốn sách có một lời giải thích tuyệt vời về việc truyền tham số trong Java và về sự khác biệt giữa thông qua tham chiếu và thông qua giá trị và nó do người tạo ra Java. Tôi sẽ khuyến khích bất cứ ai đọc nó, đặc biệt là nếu bạn vẫn chưa bị thuyết phục.
Tôi nghĩ rằng sự khác biệt giữa hai mô hình là rất tinh tế và trừ khi bạn đã thực hiện lập trình nơi bạn thực sự sử dụng tham chiếu qua, rất dễ bỏ lỡ hai mô hình khác nhau.
Tôi hy vọng điều này giải quyết cuộc tranh luận, nhưng có lẽ sẽ không.
EDIT 3
Tôi có thể là một chút ám ảnh với bài viết này. Có lẽ bởi vì tôi cảm thấy rằng các nhà sản xuất Java đã vô tình truyền bá thông tin sai lệch. Nếu thay vì sử dụng từ "tham chiếu" cho con trỏ họ đã sử dụng một cái gì đó khác, hãy nói dingleberry, sẽ không có vấn đề gì. Bạn có thể nói, "Java vượt qua dingleberries theo giá trị chứ không phải bằng tham chiếu" và không ai có thể nhầm lẫn.
Đó là lý do chỉ các nhà phát triển Java có vấn đề với điều này. Họ nhìn vào từ "tham chiếu" và nghĩ rằng họ biết chính xác điều đó có nghĩa là gì, vì vậy họ thậm chí không bận tâm đến việc xem xét lập luận trái ngược.
Dù sao, tôi nhận thấy một bình luận trong một bài viết cũ hơn, nó đã tạo ra một sự tương tự bóng mà tôi thực sự thích. Đến nỗi tôi quyết định dán một số clip-art để tạo thành một bộ phim hoạt hình để minh họa cho luận điểm.
Truyền tham chiếu theo giá trị - Thay đổi đối với tham chiếu không được phản ánh trong phạm vi của trình gọi, nhưng thay đổi đối tượng là. Điều này là do tham chiếu được sao chép, nhưng cả bản gốc và bản sao đều tham chiếu đến cùng một đối tượng.
Chuyển qua tham chiếu - Không có bản sao của tài liệu tham khảo. Tham chiếu đơn được chia sẻ bởi cả người gọi và chức năng được gọi. Mọi thay đổi đối với tham chiếu hoặc dữ liệu của Đối tượng được phản ánh trong phạm vi của người gọi.
CHỈNH SỬA 4
Tôi đã thấy các bài viết về chủ đề này mô tả việc triển khai tham số ở mức thấp trong Java, điều mà tôi nghĩ là tuyệt vời và rất hữu ích vì nó làm cho một ý tưởng trừu tượng cụ thể. Tuy nhiên, với tôi câu hỏi liên quan nhiều đến hành vi được mô tả trong đặc tả ngôn ngữ hơn là về việc thực hiện kỹ thuật của hành vi. Đây là một đoạn trích từ Đặc tả ngôn ngữ Java, phần 8.4.1 :
Khi phương thức hoặc hàm tạo được gọi (§15.12), các giá trị của biểu thức đối số thực tế khởi tạo các biến tham số mới được tạo, mỗi kiểu được khai báo, trước khi thực hiện phần thân của phương thức hoặc hàm tạo. Mã định danh xuất hiện trong DeclaratorId có thể được sử dụng như một tên đơn giản trong phần thân của phương thức hoặc hàm tạo để chỉ tham số chính thức.
Có nghĩa là, java tạo một bản sao của các tham số đã truyền trước khi thực hiện một phương thức. Giống như hầu hết những người đã nghiên cứu các trình biên dịch ở đại học, tôi đã sử dụng "The Dragon Book" đó là THE cuốn sách biên dịch. Nó có một mô tả hay về "Gọi theo giá trị" và "Gọi theo tham chiếu" trong Chương 1. Mô tả gọi theo giá trị khớp chính xác với Thông số kỹ thuật Java.
Quay trở lại khi tôi nghiên cứu các trình biên dịch - vào những năm 90, tôi đã sử dụng ấn bản đầu tiên của cuốn sách từ năm 1986 trước Java khoảng 9 hoặc 10 năm. Tuy nhiên, tôi chỉ chạy qua một bản sao của Phiên bản thứ 2 từ năm 2007 mà thực sự đề cập đến Java! Mục 1.6.6 được gắn nhãn "Cơ chế truyền tham số" mô tả tham số truyền khá độc đáo. Đây là một đoạn trích dưới tiêu đề "Gọi theo giá trị" có đề cập đến Java:
Trong cuộc gọi theo giá trị, tham số thực tế được ước tính (nếu đó là biểu thức) hoặc sao chép (nếu đó là biến). Giá trị được đặt ở vị trí thuộc về tham số chính thức tương ứng của thủ tục được gọi. Phương thức này được sử dụng trong C và Java và là một tùy chọn phổ biến trong C ++, cũng như trong hầu hết các ngôn ngữ khác.