Sự khác biệt giữa
- một tham số được truyền bằng tham chiếu
- một tham số được truyền bởi giá trị?
Bạn có thể cho tôi một số ví dụ, xin vui lòng?
Sự khác biệt giữa
Bạn có thể cho tôi một số ví dụ, xin vui lòng?
Câu trả lời:
Trước hết, sự phân biệt "vượt qua giá trị so với vượt qua tham chiếu" như được định nghĩa trong lý thuyết CS hiện đã lỗi thời vì kỹ thuật ban đầu được định nghĩa là "vượt qua tham chiếu" đã không còn được ưa chuộng và hiện nay ít được sử dụng. 1
Các ngôn ngữ mới hơn 2 có xu hướng sử dụng một cặp kỹ thuật khác (nhưng tương tự) để đạt được các hiệu ứng tương tự (xem bên dưới), đây là nguồn gây nhầm lẫn chính.
Một nguồn gây nhầm lẫn thứ cấp là trong "thông qua tham chiếu", "tham chiếu" có nghĩa hẹp hơn so với thuật ngữ chung "tham chiếu" (vì cụm từ này có trước nó).
Bây giờ, định nghĩa xác thực là:
Khi một tham số được truyền bằng tham chiếu , người gọi và callee sử dụng cùng một biến cho tham số. Nếu callee sửa đổi biến tham số, hiệu ứng sẽ hiển thị với biến của trình gọi.
Khi một tham số được truyền theo giá trị , người gọi và callee có hai biến độc lập có cùng giá trị. Nếu callee sửa đổi biến tham số, hiệu ứng sẽ không hiển thị cho người gọi.
Những điều cần lưu ý trong định nghĩa này là:
"Biến" ở đây có nghĩa là chính biến số của người gọi (cục bộ hoặc toàn cầu) - tức là nếu tôi chuyển một biến cục bộ bằng cách tham chiếu và gán cho nó, tôi sẽ tự thay đổi biến của trình gọi, chứ không phải là bất cứ thứ gì nó trỏ đến nếu đó là con trỏ .
Ý nghĩa của "tham chiếu" trong "vượt qua tham chiếu" . Sự khác biệt với thuật ngữ "tham chiếu" chung là "tham chiếu" này là tạm thời và ẩn. Những gì callee về cơ bản nhận được là một "biến" bằng cách nào đó "giống" với biến ban đầu. Hiệu ứng này đạt được cụ thể như thế nào là không liên quan (ví dụ: ngôn ngữ cũng có thể phơi bày một số chi tiết triển khai - địa chỉ, con trỏ, hội nghị - tất cả đều không liên quan; nếu hiệu ứng ròng là thế này, thì đó là tham chiếu qua).
Bây giờ, trong các ngôn ngữ hiện đại, các biến có xu hướng là "kiểu tham chiếu" (một khái niệm khác được phát minh muộn hơn "truyền bằng tham chiếu" và lấy cảm hứng từ nó), tức là dữ liệu đối tượng thực tế được lưu trữ riêng ở đâu đó (thường là trên heap) và chỉ "tham chiếu" đến nó được giữ trong các biến và được truyền dưới dạng tham số. 3
Truyền tham chiếu như vậy rơi vào giá trị truyền qua vì giá trị của biến về mặt kỹ thuật là chính tham chiếu, không phải đối tượng được tham chiếu. Tuy nhiên, hiệu ứng ròng trên chương trình có thể giống như hiệu ứng truyền qua hoặc giá trị tham chiếu:
Như bạn có thể thấy, cặp kỹ thuật này gần giống như trong định nghĩa, chỉ với một mức độ không xác định: chỉ cần thay "biến" bằng "đối tượng được tham chiếu".
Không có tên thỏa thuận cho họ, dẫn đến giải thích mâu thuẫn như "gọi theo giá trị trong đó giá trị là tham chiếu". Năm 1975, Barbara Liskov đã đề xuất thuật ngữ " chia sẻ cuộc gọi theo đối tượng " (hoặc đôi khi chỉ là "gọi bằng cách chia sẻ") mặc dù nó không bao giờ hoàn toàn bắt kịp. Hơn nữa, cả hai cụm từ này đều không song song với cặp ban đầu. Không có gì ngạc nhiên khi các điều khoản cũ cuối cùng đã được sử dụng lại trong sự vắng mặt của bất cứ điều gì tốt hơn, dẫn đến nhầm lẫn. 4
LƯU Ý : Trong một thời gian dài, câu trả lời này được sử dụng để nói:
Nói rằng tôi muốn chia sẻ một trang web với bạn. Nếu tôi cho bạn biết URL, tôi sẽ chuyển qua tham chiếu. Bạn có thể sử dụng URL đó để xem cùng một trang web tôi có thể thấy. Nếu trang đó được thay đổi, cả hai chúng ta đều thấy những thay đổi. Nếu bạn xóa URL, tất cả những gì bạn đang làm là hủy tham chiếu của bạn đến trang đó - bạn không xóa chính trang đó.
Nếu tôi in ra trang và đưa cho bạn bản in, tôi sẽ chuyển qua giá trị. Trang của bạn là một bản sao bị ngắt kết nối của bản gốc. Bạn sẽ không thấy bất kỳ thay đổi nào sau đó và mọi thay đổi bạn thực hiện (ví dụ: viết nguệch ngoạc trên bản in của bạn) sẽ không hiển thị trên trang gốc. Nếu bạn hủy bản in, bạn thực sự đã hủy bản sao của đối tượng - nhưng trang web gốc vẫn còn nguyên.
Điều này chủ yếu là chính xác ngoại trừ ý nghĩa hẹp hơn của "tham chiếu" - nó vừa tạm thời vừa ẩn (không cần, nhưng rõ ràng và / hoặc liên tục là các tính năng bổ sung, không phải là một phần của ngữ nghĩa tham chiếu qua , Như đã giải thích ở trên). Một sự tương tự gần hơn sẽ cung cấp cho bạn một bản sao của tài liệu so với việc mời bạn làm việc trên bản gốc.
1 Trừ khi bạn đang lập trình trong Fortran hoặc Visual Basic, đó không phải là hành vi mặc định và trong hầu hết các ngôn ngữ được sử dụng hiện đại, việc gọi tham chiếu thực sự thậm chí không thể thực hiện được.
2 Một số lượng lớn những người lớn tuổi cũng hỗ trợ nó
3 Trong một số ngôn ngữ hiện đại, tất cả các loại là loại tham chiếu. Cách tiếp cận này đã được tiên phong bởi ngôn ngữ CLU vào năm 1975 và từ đó đã được nhiều ngôn ngữ khác, bao gồm cả Python và Ruby chấp nhận. Và nhiều ngôn ngữ khác sử dụng cách tiếp cận hỗn hợp, trong đó một số loại là "loại giá trị" và các loại khác là "loại tham chiếu" - trong số đó có C #, Java và JavaScript.
4 Không có gì xấu khi tái chế một thuật ngữ cũ phù hợp mỗi lần, nhưng người ta phải bằng cách nào đó làm cho nó rõ ràng nghĩa được sử dụng mỗi lần. Không làm điều đó là chính xác những gì tiếp tục gây nhầm lẫn.
Đó là một cách để truyền đối số cho các hàm. Truyền bằng tham chiếu có nghĩa là tham số của các hàm được gọi sẽ giống như đối số được truyền của người gọi (không phải giá trị, mà là danh tính - chính biến). Truyền theo giá trị có nghĩa là tham số của các hàm được gọi sẽ là một bản sao của đối số được truyền của người gọi. Giá trị sẽ giống nhau, nhưng danh tính - biến - là khác nhau. Do đó, thay đổi tham số được thực hiện bởi hàm được gọi trong một trường hợp sẽ thay đổi đối số được truyền và trong trường hợp khác chỉ thay đổi giá trị của tham số trong hàm được gọi (chỉ là bản sao). Nhanh chóng:
ref
được sử dụng tại hàm người gọi và hàm được gọi). Jon Skeet cũng có một lời giải thích tốt đẹp về điều này ở đây .Mã
Vì ngôn ngữ của tôi là C ++, tôi sẽ sử dụng nó ở đây
// passes a pointer (called reference in java) to an integer
void call_by_value(int *p) { // :1
p = NULL;
}
// passes an integer
void call_by_value(int p) { // :2
p = 42;
}
// passes an integer by reference
void call_by_reference(int & p) { // :3
p = 42;
}
// this is the java style of passing references. NULL is called "null" there.
void call_by_value_special(int *p) { // :4
*p = 10; // changes what p points to ("what p references" in java)
// only changes the value of the parameter, but *not* of
// the argument passed by the caller. thus it's pass-by-value:
p = NULL;
}
int main() {
int value = 10;
int * pointer = &value;
call_by_value(pointer); // :1
assert(pointer == &value); // pointer was copied
call_by_value(value); // :2
assert(value == 10); // value was copied
call_by_reference(value); // :3
assert(value == 42); // value was passed by reference
call_by_value_special(pointer); // :4
// pointer was copied but what pointer references was changed.
assert(value == 10 && pointer == &value);
}
Và một ví dụ trong Java sẽ không bị tổn thương:
class Example {
int value = 0;
// similar to :4 case in the c++ example
static void accept_reference(Example e) { // :1
e.value++; // will change the referenced object
e = null; // will only change the parameter
}
// similar to the :2 case in the c++ example
static void accept_primitive(int v) { // :2
v++; // will only change the parameter
}
public static void main(String... args) {
int value = 0;
Example ref = new Example(); // reference
// note what we pass is the reference, not the object. we can't
// pass objects. The reference is copied (pass-by-value).
accept_reference(ref); // :1
assert ref != null && ref.value == 1;
// the primitive int variable is copied
accept_primitive(value); // :2
assert value == 0;
}
}
Wikipedia
http://en.wikipedia.org/wiki/Pass_by_reference#Call_by_value
http://en.wikipedia.org/wiki/Pass_by_reference#Call_by_reference
Anh chàng này khá nhiều móng tay đó:
Nhiều câu trả lời ở đây (và đặc biệt là câu trả lời được đánh giá cao nhất) thực tế không chính xác, vì họ hiểu sai "gọi bằng cách tham chiếu" thực sự có nghĩa là gì. Đây là nỗ lực của tôi để đặt vấn đề thẳng.
Nói một cách đơn giản nhất:
Theo nghĩa bóng:
Lưu ý rằng cả hai khái niệm này hoàn toàn độc lập và trực giao với khái niệm các kiểu tham chiếu (trong Java là tất cả các kiểu là kiểu con của Object
và tất cả các class
loại C # ) hoặc khái niệm về các loại con trỏ như trong C (tương đương về mặt ngữ nghĩa đến "các kiểu tham chiếu" của Java, chỉ đơn giản với cú pháp khác nhau).
Khái niệm loại tham chiếu tương ứng với một URL: bản thân nó là một phần thông tin và nó là một tham chiếu (một con trỏ , nếu bạn muốn) với thông tin khác. Bạn có thể có nhiều bản sao của một URL ở những nơi khác nhau và chúng không thay đổi trang web mà tất cả chúng liên kết đến; nếu trang web được cập nhật thì mọi bản sao URL sẽ vẫn dẫn đến thông tin được cập nhật. Ngược lại, việc thay đổi URL ở bất kỳ nơi nào sẽ không ảnh hưởng đến bất kỳ bản sao bằng văn bản nào khác của URL.
Lưu ý rằng C ++ có khái niệm "tài liệu tham khảo" (ví dụ int&
) có nghĩa là không như Java và C # 's 'loại tài liệu tham khảo', nhưng là giống như 'cuộc gọi bằng cách tham khảo'. "Các loại tham chiếu" của Java và C # và tất cả các loại trong Python, giống như những gì C và C ++ gọi là "các loại con trỏ" (ví dụ int*
).
OK, đây là lời giải thích dài hơn và chính thức hơn.
Để bắt đầu, tôi muốn nêu bật một số thuật ngữ quan trọng, để giúp làm rõ câu trả lời của tôi và để đảm bảo tất cả chúng ta đều đề cập đến cùng một ý tưởng khi chúng ta sử dụng từ ngữ. (Trong thực tế, tôi tin rằng phần lớn sự nhầm lẫn về các chủ đề như những chủ đề này bắt nguồn từ việc sử dụng các từ theo cách không truyền đạt đầy đủ ý nghĩa đã được dự định.)
Để bắt đầu, đây là một ví dụ trong một số ngôn ngữ giống như C của khai báo hàm:
void foo(int param) { // line 1
param += 1;
}
Và đây là một ví dụ về cách gọi hàm này:
void bar() {
int arg = 1; // line 2
foo(arg); // line 3
}
Sử dụng ví dụ này, tôi muốn xác định một số thuật ngữ quan trọng:
foo
là một hàm được khai báo trên dòng 1 (Java khăng khăng tạo ra tất cả các phương thức hàm, nhưng khái niệm này giống nhau mà không mất tính tổng quát; C và C ++ tạo ra sự khác biệt giữa khai báo và định nghĩa mà tôi sẽ không đi vào đây)param
là một tham số chính thức để foo
, cũng tuyên bố trên dòng 1arg
là một biến , cụ thể là biến cục bộ của hàm bar
, được khai báo và khởi tạo trên dòng 2arg
cũng là một đối số cho một lời mời cụ thể của foo
dòng 3Có hai bộ khái niệm rất quan trọng để phân biệt ở đây. Đầu tiên là giá trị so với biến :
bar
hàm trên, sau dòng int arg = 1;
, biểu thức arg
có giá trị 1
.final
hoặc C # readonly
) hoặc không thay đổi sâu (ví dụ: sử dụng C ++ const
).Cặp khái niệm quan trọng khác để phân biệt là tham số so với đối số :
Trong cuộc gọi theo giá trị , các tham số chính thức của hàm là các biến được tạo mới cho lệnh gọi hàm và được khởi tạo với các giá trị của các đối số của chúng.
Điều này hoạt động chính xác giống như bất kỳ loại biến nào khác được khởi tạo với các giá trị. Ví dụ:
int arg = 1;
int another_variable = arg;
Ở đây arg
và another_variable
là các biến hoàn toàn độc lập - các giá trị của chúng có thể thay đổi độc lập với nhau. Tuy nhiên, tại điểm another_variable
được khai báo, nó được khởi tạo để giữ cùng một giá trị arg
giữ - đó là 1
.
Vì chúng là các biến độc lập, các thay đổi another_variable
không ảnh hưởng đến arg
:
int arg = 1;
int another_variable = arg;
another_variable = 2;
assert arg == 1; // true
assert another_variable == 2; // true
Điều này giống hệt như mối quan hệ giữa arg
và param
trong ví dụ của chúng tôi ở trên, mà tôi sẽ lặp lại ở đây để đối xứng:
void foo(int param) {
param += 1;
}
void bar() {
int arg = 1;
foo(arg);
}
Nó chính xác như thể chúng ta đã viết mã theo cách này:
// entering function "bar" here
int arg = 1;
// entering function "foo" here
int param = arg;
param += 1;
// exiting function "foo" here
// exiting function "bar" here
Nghĩa là, đặc điểm xác định của việc gọi theo giá trị nghĩa là callee ( foo
trong trường hợp này) nhận các giá trị dưới dạng đối số, nhưng có các biến riêng cho các giá trị đó từ các biến của người gọi ( bar
trong trường hợp này).
Quay trở lại với phép ẩn dụ của tôi ở trên, nếu tôi bar
và bạn foo
, khi tôi gọi cho bạn, tôi đưa cho bạn một mảnh giấy có giá trị được viết trên đó. Bạn gọi mảnh giấy đó param
. Giá trị đó là bản sao của giá trị tôi đã ghi trong sổ ghi chép (các biến cục bộ của tôi), trong một biến tôi gọi arg
.
(Về một bên: tùy thuộc vào phần cứng và hệ điều hành, có nhiều quy ước gọi khác nhau về cách bạn gọi một chức năng từ một chức năng khác. Quy ước gọi giống như chúng tôi quyết định liệu tôi có viết giá trị lên một tờ giấy của mình không và đưa nó cho bạn hoặc nếu bạn có một mảnh giấy mà tôi viết nó hoặc nếu tôi viết nó lên tường trước mặt cả hai chúng tôi. Đây cũng là một chủ đề thú vị, nhưng vượt xa phạm vi của câu trả lời đã dài này.)
Trong cuộc gọi bằng tham chiếu , các tham số chính thức của hàm chỉ đơn giản là tên mới cho cùng các biến mà người gọi cung cấp làm đối số.
Quay trở lại ví dụ của chúng tôi ở trên, nó tương đương với:
// entering function "bar" here
int arg = 1;
// entering function "foo" here
// aha! I note that "param" is just another name for "arg"
arg /* param */ += 1;
// exiting function "foo" here
// exiting function "bar" here
Vì param
chỉ là một tên khác cho arg
- đó là, chúng là cùng một biến , thay đổi param
được phản ánh trong arg
. Đây là cách cơ bản trong đó gọi theo tham chiếu khác với gọi theo giá trị.
Rất ít ngôn ngữ hỗ trợ gọi theo tham chiếu, nhưng C ++ có thể làm như thế này:
void foo(int& param) {
param += 1;
}
void bar() {
int arg = 1;
foo(arg);
}
Trong trường hợp này, param
không chỉ có cùng giá trị như arg
, nó thực sự là arg
(chỉ bằng một tên khác) và do đó bar
có thể quan sát arg
đã được tăng lên.
Lưu ý rằng đây không phải là cách mà bất kỳ ngôn ngữ Java, JavaScript, C, Objective-C, Python hay gần như bất kỳ ngôn ngữ phổ biến nào khác hiện nay hoạt động. Điều này có nghĩa là những ngôn ngữ đó không được gọi theo tham chiếu, chúng được gọi theo giá trị.
Nếu những gì bạn có được gọi theo giá trị , nhưng giá trị thực tế là loại tham chiếu hoặc loại con trỏ , thì "giá trị" tự nó không thú vị (ví dụ: trong C, nó chỉ là một số nguyên có kích thước cụ thể của nền tảng) - những gì thú vị là những gì mà giá trị chỉ ra .
Nếu loại tham chiếu (nghĩa là con trỏ) trỏ đến là có thể thay đổi thì có thể tạo ra hiệu ứng thú vị: bạn có thể sửa đổi giá trị trỏ và người gọi có thể quan sát các thay đổi thành giá trị trỏ, mặc dù người gọi không thể quan sát thay đổi chính con trỏ
Để mượn sự tương tự của URL một lần nữa, thực tế là tôi đã đưa cho bạn một bản sao của URL tới một trang web không đặc biệt thú vị nếu điều cả hai chúng tôi quan tâm là trang web chứ không phải URL. Việc bạn viết nguệch ngoạc trên bản sao URL của bạn không ảnh hưởng đến bản sao URL của tôi không phải là điều chúng tôi quan tâm (và trên thực tế, trong các ngôn ngữ như Java và Python, "URL" hoặc giá trị loại tham chiếu, có thể hoàn toàn không được sửa đổi, chỉ có điều được chỉ bởi nó mới có thể).
Barbara Liskov, khi cô phát minh ra ngôn ngữ lập trình CLU (có các ngữ nghĩa này), nhận ra rằng các thuật ngữ hiện có "gọi theo giá trị" và "gọi theo tham chiếu" không đặc biệt hữu ích để mô tả ngữ nghĩa của ngôn ngữ mới này. Vì vậy, cô đã phát minh ra một thuật ngữ mới: gọi bằng cách chia sẻ đối tượng .
Khi thảo luận về các ngôn ngữ được gọi một cách kỹ thuật theo giá trị, nhưng trong đó các loại phổ biến được sử dụng là loại tham chiếu hoặc kiểu con trỏ (nghĩa là: gần như mọi ngôn ngữ lập trình bắt buộc, hướng đối tượng hoặc đa mô hình hiện đại), tôi thấy nó ít gây nhầm lẫn hơn chỉ cần tránh nói về cuộc gọi theo giá trị hoặc gọi theo tham chiếu . Bám sát cuộc gọi bằng cách chia sẻ đối tượng (hoặc đơn giản là gọi theo đối tượng ) và không ai bị nhầm lẫn. :-)
The first is value versus variable.
The other important pair of concepts to distinguish is parameter versus argument:
Trước khi hiểu 2 thuật ngữ, bạn PHẢI hiểu những điều sau đây. Mỗi đối tượng, có 2 điều có thể làm cho nó được phân biệt.
Vì vậy, nếu bạn nói employee.name = "John"
biết rằng có 2 điều về name
. Giá trị của nó là "John"
và cũng là vị trí của nó trong bộ nhớ là số thập lục phân có thể như thế này : 0x7fd5d258dd00
.
Tùy thuộc vào kiến trúc của ngôn ngữ hoặc loại (lớp, cấu trúc, v.v.) của đối tượng của bạn, bạn sẽ chuyển "John"
hoặc0x7fd5d258dd00
Vượt qua "John"
được gọi là vượt qua bởi giá trị. Vượt qua 0x7fd5d258dd00
được gọi là đi qua tham chiếu. Bất cứ ai đang chỉ đến vị trí bộ nhớ này sẽ có quyền truy cập vào giá trị của "John"
.
Để biết thêm về điều này, tôi khuyên bạn nên đọc về hội nghị con trỏ và cũng tại sao chọn struct (loại giá trị) trên lớp (loại tham chiếu)
Đây là một ví dụ:
#include <iostream>
void by_val(int arg) { arg += 2; }
void by_ref(int&arg) { arg += 2; }
int main()
{
int x = 0;
by_val(x); std::cout << x << std::endl; // prints 0
by_ref(x); std::cout << x << std::endl; // prints 2
int y = 0;
by_ref(y); std::cout << y << std::endl; // prints 2
by_val(y); std::cout << y << std::endl; // prints 2
}
y
đã được đặt thành 2 bởi dòng trước. Tại sao nó lại trở về 0?
Cách đơn giản nhất để có được điều này là trên một tệp Excel. Ví dụ, giả sử bạn có hai số, 5 và 2 trong các ô A1 và B1 tương ứng và bạn muốn tìm tổng của chúng trong ô thứ ba, giả sử là A2. Bạn có thể làm điều này theo hai cách.
Hoặc bằng cách chuyển các giá trị của chúng vào ô A2 bằng cách nhập = 5 + 2 vào ô này. Trong trường hợp này, nếu các giá trị của các ô A1 hoặc B1 thay đổi, tổng trong A2 vẫn giữ nguyên.
Hoặc bằng cách chuyển các tham chiếu của người dùng, điểm số của các ô A1 và B1 đến ô A2 bằng cách nhập = A1 + B1 . Trong trường hợp này, nếu các giá trị của các ô A1 hoặc B1 thay đổi, thì tổng trong A2 cũng thay đổi.
Truyền theo giá trị sẽ gửi một BẢN SAO của dữ liệu được lưu trữ trong biến bạn chỉ định, chuyển qua tham chiếu sẽ gửi một liên kết trực tiếp đến chính biến đó. Vì vậy, nếu bạn truyền một biến bằng tham chiếu và sau đó thay đổi biến bên trong khối bạn truyền vào, biến ban đầu sẽ được thay đổi. Nếu bạn chỉ đơn giản chuyển theo giá trị, biến ban đầu sẽ không thể được thay đổi bởi khối bạn đã chuyển vào nhưng bạn sẽ nhận được một bản sao của bất cứ thứ gì nó chứa trong thời gian của cuộc gọi.
Truyền theo giá trị - Hàm sao chép biến và hoạt động với một bản sao (vì vậy nó không thay đổi bất cứ điều gì trong biến ban đầu)
Truyền bằng tham chiếu - Hàm sử dụng biến ban đầu, nếu bạn thay đổi biến trong hàm khác, nó cũng thay đổi trong biến ban đầu.
Ví dụ (sao chép và sử dụng / tự thử và xem):
#include <iostream>
using namespace std;
void funct1(int a){ //pass-by-value
a = 6; //now "a" is 6 only in funct1, but not in main or anywhere else
}
void funct2(int &a){ //pass-by-reference
a = 7; //now "a" is 7 both in funct2, main and everywhere else it'll be used
}
int main()
{
int a = 5;
funct1(a);
cout<<endl<<"A is currently "<<a<<endl<<endl; //will output 5
funct2(a);
cout<<endl<<"A is currently "<<a<<endl<<endl; //will output 7
return 0;
}
Giữ cho nó đơn giản, nhìn trộm. Tường của văn bản có thể là một thói quen xấu.
Một sự khác biệt chính giữa chúng là các biến loại giá trị lưu trữ các giá trị, do đó, việc chỉ định biến loại giá trị trong lệnh gọi phương thức sẽ chuyển một bản sao của giá trị của biến đó sang phương thức. Các biến kiểu tham chiếu lưu trữ các tham chiếu đến các đối tượng, do đó, chỉ định biến kiểu tham chiếu làm đối số truyền cho phương thức một bản sao của tham chiếu thực tế tham chiếu đến đối tượng. Mặc dù bản thân tham chiếu được truyền theo giá trị, phương thức vẫn có thể sử dụng tham chiếu mà nó nhận được để tương tác với tập tin và có thể sửa đổi đối tượng ban đầu. Tương tự, khi trả về thông tin từ một phương thức thông qua câu lệnh return, phương thức trả về một bản sao của giá trị được lưu trữ trong một biến loại giá trị hoặc một bản sao của tham chiếu được lưu trữ trong một biến kiểu tham chiếu. Khi một tham chiếu được trả về, phương thức gọi có thể sử dụng tham chiếu đó để tương tác với đối tượng được tham chiếu. Vì thế,
Trong c #, để truyền một biến bằng tham chiếu để phương thức được gọi có thể sửa đổi biến, C # cung cấp từ khóa ref và out. Áp dụng từ khóa ref cho một khai báo tham số cho phép bạn truyền một biến cho một phương thức bằng cách tham chiếu, phương thức được gọi sẽ có thể sửa đổi biến ban đầu trong trình gọi. Từ khóa ref được sử dụng cho các biến đã được khởi tạo trong phương thức gọi. Thông thường, khi một cuộc gọi phương thức chứa một biến chưa được khởi tạo làm đối số, trình biên dịch sẽ tạo ra một lỗi. Trước một tham số với từ khóa ra sẽ tạo ra một tham số đầu ra. Điều này chỉ ra cho trình biên dịch rằng đối số sẽ được truyền vào phương thức được gọi bằng tham chiếu và phương thức được gọi sẽ gán giá trị cho biến ban đầu trong trình gọi. Nếu phương thức không gán giá trị cho tham số đầu ra trong mọi đường dẫn thực thi có thể, trình biên dịch sẽ tạo ra lỗi. Điều này cũng ngăn trình biên dịch tạo ra một thông báo lỗi cho một biến chưa được khởi tạo được truyền dưới dạng đối số cho một phương thức. Một phương thức chỉ có thể trả về một giá trị cho trình gọi của nó thông qua câu lệnh return, nhưng có thể trả về nhiều giá trị bằng cách chỉ định nhiều tham số đầu ra (ref và / hoặc out).
xem thảo luận c # và ví dụ ở đây liên kết văn bản
Ví dụ:
class Dog
{
public:
barkAt( const std::string& pOtherDog ); // const reference
barkAt( std::string pOtherDog ); // value
};
const &
nói chung là tốt nhất Bạn không phải chịu hình phạt xây dựng và phá hủy. Nếu tham chiếu không phải là giao diện của bạn thì nó sẽ gợi ý rằng nó sẽ thay đổi dữ liệu được truyền.
Nếu bạn không muốn thay đổi giá trị của biến ban đầu sau khi chuyển nó vào hàm, hàm sẽ được xây dựng với tham số " truyền theo giá trị ".
Sau đó, hàm sẽ CHỈ có giá trị nhưng không có địa chỉ của biến được truyền trong biến. Không có địa chỉ của biến, mã bên trong hàm không thể thay đổi giá trị biến như nhìn thấy từ bên ngoài hàm.
Nhưng nếu bạn muốn cung cấp cho hàm khả năng thay đổi giá trị của biến khi nhìn từ bên ngoài, bạn cần sử dụng pass bằng tham chiếu . Vì cả giá trị và địa chỉ (tham chiếu) đều được truyền vào và có sẵn bên trong hàm.
vượt qua giá trị có nghĩa là làm thế nào để truyền giá trị cho hàm bằng cách sử dụng các đối số. để vượt qua giá trị, chúng tôi sao chép dữ liệu được lưu trữ trong biến mà chúng tôi chỉ định và tốc độ chậm hơn so với chuyển qua tham chiếu bởi vì dữ liệu được sao chép. trong số chúng tôi thực hiện thay đổi dữ liệu sao chép, dữ liệu gốc không bị ảnh hưởng. nd trong pass bằng cách giới thiệu hoặc chuyển qua địa chỉ, chúng tôi gửi liên kết trực tiếp đến chính biến đó. hoặc chuyển con trỏ đến một biến. nó nhanh hơn bcse ít thời gian hơn được tiêu thụ
Dưới đây là một ví dụ minh họa sự khác biệt giữa truyền theo giá trị - giá trị con trỏ - tham chiếu :
void swap_by_value(int a, int b){
int temp;
temp = a;
a = b;
b = temp;
}
void swap_by_pointer(int *a, int *b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}
void swap_by_reference(int &a, int &b){
int temp;
temp = a;
a = b;
b = temp;
}
int main(void){
int arg1 = 1, arg2 = 2;
swap_by_value(arg1, arg2);
cout << arg1 << " " << arg2 << endl; //prints 1 2
swap_by_pointer(&arg1, &arg2);
cout << arg1 << " " << arg2 << endl; //prints 2 1
arg1 = 1; //reset values
arg2 = 2;
swap_by_reference(arg1, arg2);
cout << arg1 << " " << arg2 << endl; //prints 2 1
}
Việc chuyển qua bằng phương pháp tham chiếu trực tiếp có một giới hạn quan trọng . Nếu một tham số được khai báo là được truyền bởi tham chiếu (vì vậy nó được đặt trước dấu &) thì tham số thực tế tương ứng của nó phải là một biến .
Một tham số thực tế đề cập đến các dòng được truyền bởi giá trị Tham số chính thức có thể là một biểu thức nói chung, do đó, nó được phép sử dụng không chỉ một biến mà còn là kết quả của một lời gọi hàm hoặc thậm chí là hàm.
Hàm không thể đặt một giá trị trong một cái gì đó ngoài một biến. Nó không thể gán giá trị mới cho một nghĩa đen hoặc buộc một biểu thức thay đổi kết quả của nó.
PS: Bạn cũng có thể kiểm tra câu trả lời của Dylan Beattie trong chuỗi hiện tại giải thích nó bằng những từ đơn giản.