Sự khác biệt giữa chuyển qua tham chiếu so với chuyển qua giá trị là gì?


562

Sự khác biệt giữa

  1. một tham số được truyền bằng tham chiếu
  2. 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?



1
Nếu bạn không biết địa chỉ hoặc giá trị là gì thì hãy xem tại đây
Honey

Câu trả lời:


1079

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ờikỹ 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ỏ .

    • Điều này hiện được coi là thực hành xấu (như một phụ thuộc ngầm). Như vậy, hầu như tất cả các ngôn ngữ mới hơn là độc quyền hoặc gần như chỉ có giá trị truyền qua. Tham chiếu qua hiện được sử dụng chủ yếu dưới dạng "đối số đầu ra / đầu ra" trong các ngôn ngữ mà hàm không thể trả về nhiều hơn một giá 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:

  • Nếu một tham chiếu chỉ được lấy từ biến của người gọi và được truyền dưới dạng đối số, thì điều này có tác dụng tương tự như tham chiếu qua: nếu đối tượng được tham chiếu bị thay đổi trong callee, người gọi sẽ thấy sự thay đổi.
    • Tuy nhiên, nếu một biến giữ tham chiếu này được xác định lại, nó sẽ dừng trỏ đến đối tượng đó, do đó, mọi hoạt động tiếp theo trên biến này sẽ thay vào đó ảnh hưởng đến bất cứ điều gì nó đang trỏ đến bây giờ.
  • Để có hiệu ứng tương tự như pass-by-value, một bản sao của đối tượng được tạo ra tại một số điểm. Các tùy chọn bao gồm:
    • Người gọi chỉ có thể tạo một bản sao riêng tư trước cuộc gọi và thay vào đó hãy tham khảo.
    • Trong một số ngôn ngữ, một số loại đối tượng là "bất biến": mọi thao tác trên chúng dường như làm thay đổi giá trị thực sự tạo ra một đối tượng hoàn toàn mới mà không ảnh hưởng đến đối tượng ban đầu. Vì vậy, việc truyền một đối tượng thuộc loại như một đối số luôn có tác dụng của giá trị truyền qua: một bản sao cho callee sẽ được tạo tự động nếu và khi nó cần thay đổi và đối tượng của người gọi sẽ không bao giờ bị ảnh hưởng.
      • Trong các ngôn ngữ chức năng, tất cả các đối tượng là bất biến.

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.


Cá nhân tôi sẽ sử dụng thuật ngữ "mới" hoặc "gián tiếp" giá trị / thông qua tham chiếu cho các kỹ thuật mới.
ivan_pozdeev

Định nghĩa xác thực của Nhật Bản mà bạn cung cấp không phải là định nghĩa được đưa ra trong hầu hết các khóa học lập trình giới thiệu. Google thông qua tham chiếu là gì và bạn sẽ không nhận được câu trả lời đó. Định nghĩa xác thực bạn cung cấp là sử dụng sai từ tham chiếu từ, vì khi bạn theo định nghĩa đó, bạn đang sử dụng bí danh không phải là tham chiếu: bạn có hai biến thực sự là cùng một biến, đó là bí danh và không phải là tham chiếu. Định nghĩa xác thực của bạn gây ra nhầm lẫn hàng loạt không có lý do. Chỉ cần nói vượt qua tham chiếu có nghĩa là vượt qua địa chỉ. Nó có ý nghĩa và sẽ tránh sự nhầm lẫn vô nghĩa này.
YungGun

@YungGun 1) Vui lòng cung cấp một liên kết đến một "định nghĩa được đưa ra trong hầu hết các khóa học lập trình giới thiệu". Cũng lưu ý rằng điều này nhằm mục đích rõ ràng trong thực tế ngày nay, không phải trong thực tế của một thập kỷ hoặc ba năm trước khi một số khóa học CS được viết. 2) "Địa chỉ" không thể được sử dụng trong định nghĩa vì nó cố tình trừu tượng hóa từ các triển khai có thể. Ví dụ: một số ngôn ngữ (Fortran) không có con trỏ; họ cũng khác nhau về việc họ có để lộ địa chỉ thô cho người dùng hay không (VB không); nó cũng không phải là một địa chỉ bộ nhớ thô, bất cứ điều gì cho phép liên kết đến biến sẽ làm.
ivan_pozdeev

@ivan_podeev không có liên kết xin lỗi. Tôi nói "gần như mọi khóa học giới thiệu" bởi vì cá nhân tôi, tôi đã đi học đại học và học lập trình bootcamp cũng dạy tôi điều đó. Các khóa học này là hiện đại (ít hơn 5 năm trước). "Địa chỉ thô" là từ đồng nghĩa với "con trỏ" ... Bạn có thể đúng về mặt kỹ thuật (theo một số liên kết được chọn bằng cherry) nhưng ngôn ngữ bạn đang sử dụng là không thực tế và gây nhầm lẫn cho hầu hết các lập trình viên. Nếu bạn muốn suy nghĩ đầy đủ của tôi về nó, tôi đã viết một bài đăng trên blog 3500 từ: Medium.com/@isaaccway228/iêu
YungGun

@YungGun "quá lâu, không đọc". Nhìn thoáng qua cho thấy chính xác những nhầm lẫn được nêu trong câu trả lời. Thông qua tham chiếu là một kỹ thuật trừu tượng bất khả tri để thực hiện. Nó không quan trọng chính xác những gì được thông qua dưới mui xe, nó quan trọng ảnh hưởng đến chương trình là gì.
ivan_pozdeev

150

Đó 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:

  • Java chỉ hỗ trợ truyền theo giá trị. Luôn sao chép các đối số, mặc dù khi sao chép tham chiếu đến một đối tượng, tham số trong hàm được gọi sẽ trỏ đến cùng một đối tượng và thay đổi đối tượng đó sẽ được nhìn thấy trong trình gọi. Vì điều này có thể gây nhầm lẫn, đây là những gì Jon Skeet nói về điều này.
  • C # hỗ trợ truyền theo giá trị và chuyển qua tham chiếu (từ khóa 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 .
  • C ++ hỗ trợ truyền theo giá trị và truyền theo tham chiếu (loại tham số tham chiếu được sử dụng tại hàm được gọi). Bạn sẽ tìm thấy một lời giải thích về điều này dưới đây.

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 đó:

http://javadude.com/articles/passbyvalue.htmlm


9
Tại sao các downvote? nếu có gì sai hoặc dẫn đến hiểu lầm xin vui lòng để lại nhận xét.
Julian Schaub - litb

1
Không phải là phiếu bầu của tôi, nhưng ở một điểm nhỏ (bạn biết đấy, loại được đưa ra trong các cuộc tranh luận tổng thống) Tôi sẽ nói đó là một "chiến thuật" hơn là một "chiến lược".
harpo

28
+1 cho tính đầy đủ. Đừng lo lắng về downvote - mọi người làm điều đó vì những lý do kỳ lạ. Trong một câu hỏi ý kiến ​​về máy tính, mọi người đều bị hạ thấp bởi một anh chàng không nghĩ rằng các lập trình viên nên sử dụng máy tính! Dù sao, tôi nghĩ rằng câu trả lời của bạn là rất tốt.
Mark Britsham

1
Các liên kết đến lời giải thích của Skeet bị hỏng.
Lập trình viên định hướng tiền bạc

Các liên kết đến lời giải thích của Skeet vẫn bị hỏng.
Rokit

85

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.

TL; DR

Nói một cách đơn giản nhất:

  • gọi theo giá trị nghĩa là bạn chuyển các giá trị dưới dạng đối số hàm
  • gọi theo tham chiếu có nghĩa là bạn chuyển các biến làm đối số hàm

Theo nghĩa bóng:

  • Gọi theo giá trị là nơi tôi viết một cái gì đó lên một tờ giấy và đưa nó cho bạn . Có thể đó là một URL, có thể đó là bản sao hoàn chỉnh của Chiến tranh và Hòa bình. Cho dù đó là gì, nó nằm trên một mảnh giấy mà tôi đã đưa cho bạn, và vì vậy bây giờ nó thực sự là mảnh giấy của bạn . Bây giờ bạn có thể tự do viết nguệch ngoạc trên mảnh giấy đó, hoặc sử dụng mảnh giấy đó để tìm một cái gì đó ở một nơi khác và mân mê nó, bất cứ điều gì.
  • Gọi bằng cách tham khảo là khi tôi đưa cho bạn cuốn sổ tay của tôi có một cái gì đó được viết trong đó . Bạn có thể viết nguệch ngoạc vào sổ ghi chép của tôi (có thể tôi muốn bạn, có thể tôi không), và sau đó tôi giữ sổ ghi chép của mình, với bất kỳ nét vẽ nguệch ngoạc nào bạn đặt ở đó. Ngoài ra, nếu những gì bạn hoặc tôi viết có thông tin về cách tìm thứ gì đó ở nơi khác, bạn hoặc tôi có thể đến đó và tìm hiểu thông tin đó.

"Gọi theo giá trị" và "gọi theo tham chiếu" không có nghĩa là gì

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 Objectvà tất cả các classloạ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 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.

Thuật ngữ

Để 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:

  • foolà 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)
  • paramlà một tham số chính thức để foo, cũng tuyên bố trên dòng 1
  • arglà 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 2
  • argcũng là một đối số cho một lời mời cụ thể của foodòng 3

Có 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 :

  • Một giá trịkết quả của việc đánh giá một biểu thức trong ngôn ngữ. Ví dụ, trong barhàm trên, sau dòng int arg = 1;, biểu thức arggiá trị 1 .
  • Một biến là một thùng chứa cho các giá trị . Một biến có thể thay đổi (đây là mặc định trong hầu hết các ngôn ngữ giống như C), chỉ đọc (ví dụ: được khai báo bằng Java finalhoặ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ố :

  • Một tham số (còn được gọi là tham số chính thức ) là một biến phải được cung cấp bởi người gọi khi gọi hàm.
  • Một lập luận là một giá trị được cung cấp bởi người gọi của một chức năng để đáp ứng một tham số hình thức cụ thể của chức năng đó

Gọi theo giá trị

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 arganother_variablelà 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ị arggiữ - đó là 1.

Vì chúng là các biến độc lập, các thay đổi another_variablekhô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 argparamtrong 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 ( footrong 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 ( bartrong 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 barvà 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.)

Gọi bằng cách tham khảo

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

paramchỉ 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, paramkhông chỉ có cùng giá trị như arg, nó thực sự arg (chỉ bằng một tên khác) và do đó barcó 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ị.

Phụ lục: gọi bằng cách chia sẻ đối tượng

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. :-)


Giải thích rõ hơn: Có hai bộ khái niệm rất quan trọng để phân biệt ở đây. The first is value versus variable. The other important pair of concepts to distinguish is parameter versus argument:
SK Venkat

2
Câu trả lời tuyệt vời. Tôi nghĩ rằng tôi sẽ thêm rằng không cần lưu trữ mới để vượt qua bằng cách tham chiếu. Tên tham số tham chiếu bộ nhớ gốc (bộ nhớ)
Cảm ơn

1
Câu trả lời hay nhất IMO
Rafael Eyng

59

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.

  • Giá trị của nó.
  • Địa chỉ của nó.

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)


3
Đó là tôi đang tìm kiếm, thực sự người ta nên tìm khái niệm không chỉ là lời giải thích, giơ ngón tay cái lên.
Haisum Usman

Java luôn luôn vượt qua giá trị. Truyền tham chiếu đến các đối tượng trong java được coi là truyền theo giá trị. Điều này mâu thuẫn với tuyên bố của bạn "Vượt qua 0x7fd5d258dd00 được gọi là chuyển qua tham chiếu."
chetan raina

53

Đâ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
}

1
Tôi nghĩ có một vấn đề là dòng cuối cùng nên in 0 thay vì 2. Vui lòng cho tôi biết nếu tôi thiếu thứ gì đó.
Taimoor Changaiz

@TaimoorChangaiz; "Dòng cuối" nào? Nhân tiện, nếu bạn có thể sử dụng IRC, vui lòng đến với ## lập trình trên Freenode. Nó sẽ dễ dàng hơn rất nhiều để giải thích mọi thứ ở đó. Nick của tôi có "pyon".
pyon

1
@ EduardoLeón by_val (y); std :: cout << y << std :: endl; // in 2
Taimoor Changaiz

5
@TaimoorChangaiz: Tại sao nó không in 2? yđã được đặt thành 2 bởi dòng trước. Tại sao nó lại trở về 0?
pyon

@ EduardoLeón xấu của tôi. vâng bạn đúng Cảm ơn đã sửa chữa
Taimoor Changaiz

28

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.


Đây là ví dụ đơn giản nhất và tốt nhất trong số tất cả các câu trả lời khác.
Amit Ray

18

Khi đi qua ref, về cơ bản bạn đang chuyển một con trỏ tới biến. Truyền theo giá trị bạn đang truyền một bản sao của biến. Trong sử dụng cơ bản, điều này thường có nghĩa là vượt qua các thay đổi ref cho biến sẽ được xem là phương thức gọi và chuyển theo giá trị mà chúng sẽ không.


12

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.


7

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.


Điều này thực sự hữu ích trong việc tìm hiểu xem giá trị tham số có bị thay đổi hay không, cảm ơn!
Kevin Zhao

5

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


3

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.


2

Nói tóm lại, Đạt theo giá trị là GÌ và thông qua tham chiếu là Ở ĐÂU.

Nếu giá trị của bạn là VAR1 = "Happy Guy!", Bạn sẽ chỉ thấy "Happy Guy!". Nếu VAR1 thay đổi thành "Happy Gal!", Bạn sẽ không biết điều đó. Nếu nó được chuyển qua tham chiếu và VAR1 thay đổi, bạn sẽ làm được.


2

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.


1

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ụ


0

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.


Bạn nói "nếu một tham số được khai báo [như một tham chiếu] tham số thực tế tương ứng của nó phải là một biến", nhưng nói chung điều đó không đúng. Nếu tham chiếu bị ràng buộc tạm thời (chẳng hạn như giá trị trả về của hàm), thời gian tồn tại của nó được kéo dài để phù hợp với tham chiếu. Xem ở đây để biết chi tiết.
Chris Hunt
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.