Apa perbedaan antara meneruskan dengan referensi vs meneruskan dengan nilai?

Jawaban:

1080

Pertama dan terutama, perbedaan "pass by value vs. pass by reference" seperti yang didefinisikan dalam teori CS sekarang sudah usang karena teknik yang awalnya didefinisikan sebagai "pass by reference" sejak itu tidak disukai dan jarang digunakan sekarang. 1

Bahasa yang lebih baru 2 cenderung menggunakan pasangan teknik yang berbeda (tetapi serupa) untuk mencapai efek yang sama (lihat di bawah) yang merupakan sumber utama kebingungan.

Sumber kebingungan kedua adalah fakta bahwa dalam "pass by reference", "reference" memiliki arti yang lebih sempit daripada istilah umum "reference" (karena frasa yang mendahului).


Sekarang, definisi otentik adalah:

  • Ketika suatu parameter dilewatkan dengan referensi , pemanggil dan callee menggunakan variabel yang sama untuk parameter. Jika callee memodifikasi variabel parameter, efeknya terlihat oleh variabel pemanggil.

  • Ketika suatu parameter dilewatkan oleh nilai , penelepon dan callee memiliki dua variabel independen dengan nilai yang sama. Jika callee memodifikasi variabel parameter, efeknya tidak terlihat oleh pemanggil.

Hal yang perlu diperhatikan dalam definisi ini adalah:

  • "Variabel" di sini berarti variabel penelepon (lokal atau global) itu sendiri - yaitu jika saya melewatkan variabel lokal dengan referensi dan menugaskannya, saya akan mengubah variabel penelepon itu sendiri, bukan misalnya apa pun yang menunjuk jika itu adalah penunjuk .

    • Ini sekarang dianggap praktik buruk (sebagai ketergantungan implisit). Dengan demikian, secara virtual semua bahasa yang lebih baru secara eksklusif, atau hampir secara eksklusif dinilai sebagai nilai tambah. Pass-by-reference sekarang terutama digunakan dalam bentuk "argumen output / inout" dalam bahasa di mana fungsi tidak dapat mengembalikan lebih dari satu nilai.
  • Arti "referensi" dalam "lewat referensi" . Perbedaannya dengan istilah "referensi" umum adalah bahwa "referensi" ini bersifat sementara dan implisit. Apa yang pada dasarnya didapatkan oleh callee adalah "variabel" yang entah bagaimana "sama" dengan yang asli. Seberapa spesifik efek ini dicapai adalah tidak relevan (mis. Bahasa juga dapat mengekspos beberapa detail implementasi - alamat, pointer, dereferencing - ini semua tidak relevan; jika efek bersihnya adalah ini, ini hanya sebagai referensi).


Sekarang, dalam bahasa modern, variabel cenderung berasal dari "tipe referensi" (konsep lain ditemukan lebih lambat dari "lewat referensi" dan diilhami olehnya), yaitu data objek aktual disimpan secara terpisah di suatu tempat (biasanya, di heap), dan hanya "referensi" yang pernah disimpan dalam variabel dan dikirimkan sebagai parameter. 3

Melewati referensi seperti itu jatuh di bawah nilai by-pass karena nilai variabel secara teknis referensi itu sendiri, bukan objek yang dirujuk. Namun, efek bersih pada program bisa sama dengan pass-by-value atau pass-by-reference:

  • Jika referensi baru saja diambil dari variabel penelepon dan dilewatkan sebagai argumen, ini memiliki efek yang sama dengan pass-by-reference: jika objek yang dirujuk dimatikan dalam callee, penelepon akan melihat perubahan.
    • Namun, jika variabel yang memegang referensi ini dikaji ulang, itu akan berhenti menunjuk ke objek itu, jadi setiap operasi lebih lanjut pada variabel ini malah akan mempengaruhi apa pun yang ditunjuknya sekarang.
  • Untuk memiliki efek yang sama dengan nilai per detik, salinan objek dibuat di beberapa titik. Opsi meliputi:
    • Penelepon hanya dapat membuat salinan pribadi sebelum panggilan dan memberikan callee referensi untuk itu.
    • Dalam beberapa bahasa, beberapa jenis objek "tidak berubah": operasi apa pun pada mereka yang tampaknya mengubah nilai sebenarnya menciptakan objek yang sama sekali baru tanpa mempengaruhi yang asli. Jadi, melewatkan objek dengan tipe seperti argumen selalu memiliki efek pass-by-value: salinan untuk callee akan dibuat secara otomatis jika dan ketika perlu perubahan, dan objek pemanggil tidak akan pernah terpengaruh.
      • Dalam bahasa fungsional, semua objek tidak berubah.

Seperti yang Anda lihat, pasangan teknik ini hampir sama dengan yang ada di definisi, hanya dengan tingkat tipuan: ganti saja "variabel" dengan "objek yang direferensikan".

Tidak ada nama yang disepakati untuk mereka, yang mengarah ke penjelasan yang berkerut seperti "panggilan berdasarkan nilai di mana nilainya adalah referensi". Pada tahun 1975, Barbara Liskov menyarankan istilah " call-by-object-sharing " (atau kadang-kadang hanya "call-by-sharing") meskipun tidak pernah cukup populer. Selain itu, tidak satu pun dari frasa ini yang sejajar dengan pasangan aslinya. Tidak heran istilah lama akhirnya digunakan kembali tanpa adanya sesuatu yang lebih baik, yang menyebabkan kebingungan. 4


CATATAN : Untuk waktu yang lama, jawaban ini digunakan untuk mengatakan:

Katakanlah saya ingin berbagi halaman web dengan Anda. Jika saya memberi tahu Anda URL, saya memberikan referensi. Anda dapat menggunakan URL itu untuk melihat halaman web yang sama dengan yang saya lihat. Jika halaman itu diubah, kami berdua melihat perubahannya. Jika Anda menghapus URL, yang Anda lakukan adalah menghancurkan referensi Anda ke halaman itu - Anda tidak menghapus halaman itu sendiri.

Jika saya mencetak halaman dan memberi Anda hasil cetak, saya memberikan nilai. Halaman Anda adalah salinan asli dari yang terputus. Anda tidak akan melihat perubahan berikutnya, dan perubahan apa pun yang Anda buat (mis. Mencoret-coret pada cetakan Anda) tidak akan muncul di halaman asli. Jika Anda menghancurkan cetakan, Anda sebenarnya telah menghancurkan salinan objek - tetapi halaman web asli tetap utuh.

Ini sebagian besar benar kecuali arti yang lebih sempit dari "referensi" - itu bersifat sementara dan implisit (tidak harus, tetapi menjadi eksplisit dan / atau persisten adalah fitur tambahan, bukan bagian dari semantik pass-by-reference , seperti dijelaskan di atas). Analogi yang lebih dekat akan memberi Anda salinan dokumen vs mengundang Anda untuk mengerjakan yang asli.


1 Kecuali Anda memprogram dalam Fortran atau Visual Basic, itu bukan perilaku default, dan dalam sebagian besar bahasa dalam penggunaan modern, panggilan-dengan-referensi yang sebenarnya bahkan tidak mungkin.

2 Sejumlah yang lebih tua juga mendukungnya

3 Dalam beberapa bahasa modern, semua tipe adalah tipe referensi. Pendekatan ini dipelopori oleh bahasa CLU pada tahun 1975 dan sejak itu telah diadopsi oleh banyak bahasa lain, termasuk Python dan Ruby. Dan masih banyak lagi bahasa yang menggunakan pendekatan hybrid, di mana beberapa tipe adalah "tipe nilai" dan yang lain adalah "tipe referensi" - di antaranya adalah C #, Java, dan JavaScript.

4 Tidak ada yang buruk dengan mendaur ulang istilah lama yang pas , tapi kita harus membuatnya dengan jelas makna mana yang digunakan setiap kali. Tidak melakukan hal itulah yang menyebabkan kebingungan.

ivan_pozdeev
sumber
Saya pribadi akan menggunakan istilah pass-by-value / pass-by-value "baru" atau "tidak langsung" untuk teknik-teknik baru.
ivan_pozdeev
Definisi "asli" yang Anda berikan bukan definisi yang diberikan di hampir setiap kursus pemrograman pengantar. Google yang lulus referensi dan Anda tidak akan mendapatkan jawaban itu. Definisi otentik yang Anda berikan adalah penyalahgunaan referensi kata, seperti ketika Anda mengikuti definisi yang Anda gunakan alias bukan referensi: Anda memiliki dua variabel yang sebenarnya variabel yang sama, yaitu alias dan bukan referensi. Definisi otentik Anda menyebabkan kebingungan massal tanpa alasan. Katakan saja lewat referensi berarti lewat alamat. Masuk akal dan akan menghindari kebingungan yang tidak ada gunanya ini.
YungGun
@YungGun 1) Berikan tautan ke "definisi yang diberikan di hampir setiap kursus pemrograman pengantar". Juga perhatikan bahwa ini bertujuan untuk menjadi jelas dalam realitas hari ini, bukan dalam kenyataan satu atau tiga dekade yang lalu ketika beberapa kursus CS ditulis. 2) "Alamat" tidak dapat digunakan dalam definisi karena sengaja disarikan dari kemungkinan implementasi. Misalnya beberapa bahasa (Fortran) tidak memiliki petunjuk; mereka juga berbeda dalam apakah mereka mengekspos alamat mentah kepada pengguna (VB tidak); itu juga tidak harus menjadi alamat memori mentah, apa pun yang memungkinkan tautan ke variabel akan dilakukan.
ivan_pozdeev
@ivan_podeev tidak ada tautan maaf Saya mengatakan "hampir setiap kursus pengantar" karena secara pribadi, saya kuliah dan mengambil bootcamp pemrograman yang mengajari saya hal itu. Kursus-kursus ini modern (kurang dari 5 tahun yang lalu). "Alamat mentah" adalah sinonim untuk "penunjuk" ... Anda mungkin secara teknis benar (menurut beberapa tautan yang dipilih), tetapi bahasa yang Anda gunakan tidak praktis dan membingungkan bagi sebagian besar programmer. Jika Anda ingin pemikiran penuh saya di atasnya, saya telah menulis posting blog 3.500 kata: medium.com/@isaaccway228/…
YungGun
@YungGun "terlalu lama, tidak membaca". Pandangan sekilas menunjukkan kebingungan yang diuraikan dalam jawaban. Lulus dengan referensi adalah teknik abstrak agnostik untuk implementasi. Tidak masalah apa yang sebenarnya dilewatkan di bawah tenda, itu penting apa efeknya pada program ini.
ivan_pozdeev
150

Ini adalah cara bagaimana menyampaikan argumen ke fungsi. Melewati dengan referensi berarti parameter fungsi yang dipanggil akan sama dengan argumen yang dilewati oleh penelepon (bukan nilai, tetapi identitas - variabel itu sendiri). Lewat nilai berarti parameter fungsi yang dipanggil akan menjadi salinan dari argumen yang dilewati penelepon. Nilainya akan sama, tetapi identitas - variabel - berbeda. Jadi perubahan pada parameter yang dilakukan oleh fungsi yang dipanggil dalam satu kasus mengubah argumen yang diteruskan dan dalam kasus lain hanya mengubah nilai parameter dalam fungsi yang disebut (yang hanya merupakan salinan). Terburu-buru cepat:

  • Java hanya mendukung nilai by pass. Selalu menyalin argumen, meskipun saat menyalin referensi ke objek, parameter dalam fungsi yang dipanggil akan menunjuk ke objek yang sama dan perubahan ke objek itu akan terlihat di pemanggil. Karena ini bisa membingungkan, inilah yang dikatakan Jon Skeet tentang ini.
  • C # mendukung pass by value dan pass by reference (kata kunci yang refdigunakan pada fungsi pemanggil dan dipanggil). Jon Skeet juga memiliki penjelasan yang bagus tentang ini di sini .
  • C ++ mendukung pass by value dan pass by reference (tipe parameter referensi digunakan pada fungsi yang disebut). Anda akan menemukan penjelasan di bawah ini.

Kode

Karena bahasa saya adalah C ++, saya akan menggunakannya di sini

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

Dan contoh di Jawa tidak akan sakit:

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

Orang ini cukup berhasil:

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

Johannes Schaub - litb
sumber
9
mengapa downvote? jika ada sesuatu yang salah atau mengarah pada kesalahpahaman, silakan tinggalkan komentar.
Johannes Schaub - litb
1
Bukan suara saya, tetapi pada titik kecil (Anda tahu, jenis diambil dalam debat presiden) Saya akan mengatakan itu lebih dari "taktik" daripada "strategi."
harpo
28
+1 untuk Kelengkapan. Jangan khawatir tentang downvotes - orang melakukannya karena alasan aneh. Dalam sebuah pertanyaan pendapat tentang kalkulator, semua orang diturunkan suara oleh seorang pria yang tidak berpikir programmer harus menggunakan kalkulator! Bagaimanapun, saya pikir jawaban Anda sangat bagus.
Mark Brittingham
1
Tautan ke penjelasan Skeet terputus.
Programmer Berorientasi Uang
Tautan ke penjelasan Skeet masih terputus.
Rokit
85

Banyak jawaban di sini (dan khususnya jawaban yang paling banyak dipilih) secara faktual salah, karena mereka salah mengerti apa arti "panggilan dengan referensi". Inilah upaya saya untuk meluruskan masalah.

TL; DR

Secara sederhana:

  • panggilan berdasarkan nilai berarti Anda memberikan nilai sebagai argumen fungsi
  • panggilan dengan referensi berarti Anda meneruskan variabel sebagai argumen fungsi

Dalam istilah metaforis:

  • Panggil berdasarkan nilai adalah tempat saya menuliskan sesuatu di selembar kertas dan memberikannya kepada Anda . Mungkin itu URL, mungkin salinan lengkap War and Peace. Tidak peduli apa itu, itu di selembar kertas yang telah saya berikan kepada Anda, dan sekarang ini adalah selembar kertas Anda secara efektif . Anda sekarang bebas untuk mencoret-coret kertas itu, atau menggunakan kertas itu untuk menemukan sesuatu di tempat lain dan mengutak-atiknya, apa pun.
  • Panggil dengan referensi adalah ketika saya memberi Anda buku catatan saya yang memiliki sesuatu yang tertulis di dalamnya . Anda dapat mencoret-coret di buku catatan saya (mungkin saya menginginkannya, mungkin juga tidak), dan setelah itu saya menyimpan buku catatan saya, dengan coretan apa pun yang telah Anda masukkan ke sana. Juga, jika apa yang Anda atau saya tulis ada informasi tentang bagaimana menemukan sesuatu di tempat lain, Anda atau saya bisa pergi ke sana dan mengutak-atik informasi itu.

Apa "panggilan berdasarkan nilai" dan "panggilan dengan referensi" tidak berarti

Perhatikan bahwa kedua konsep ini sepenuhnya independen dan ortogonal dari konsep tipe referensi (yang di Jawa adalah semua tipe yang merupakan subtipe dari Object, dan dalam semua classtipe C ), atau konsep tipe pointer seperti dalam C (yang secara semantik setara untuk "tipe referensi" Java, hanya dengan sintaks yang berbeda).

Gagasan tipe referensi sesuai dengan URL: itu sendiri merupakan sepotong informasi, dan merupakan referensi (a pointer , jika Anda mau) ke informasi lain. Anda dapat memiliki banyak salinan URL di tempat yang berbeda, dan mereka tidak mengubah situs web yang mereka tautkan; jika situs web diperbarui maka setiap salinan URL masih akan mengarah pada informasi yang diperbarui. Sebaliknya, mengubah URL di satu tempat tidak akan memengaruhi salinan tertulis URL lainnya.

Perhatikan bahwa C ++ memiliki gagasan "referensi" (misalnya int&) yang tidak seperti Java dan C # 's 'referensi jenis', tetapi adalah seperti 'panggilan dengan referensi'. "Tipe referensi" Java dan C #, dan semua tipe dalam Python, seperti apa yang C dan C ++ sebut "tipe pointer" (mis int*.).


OKE, inilah penjelasan yang lebih panjang dan lebih formal.

Terminologi

Untuk mulai dengan, saya ingin menyoroti beberapa terminologi penting, untuk membantu memperjelas jawaban saya dan untuk memastikan kita semua mengacu pada ide yang sama ketika kita menggunakan kata-kata. (Dalam praktiknya, saya percaya sebagian besar kebingungan tentang topik-topik seperti ini berasal dari penggunaan kata-kata dengan cara yang tidak sepenuhnya mengomunikasikan makna yang dimaksudkan.)

Untuk memulai, berikut adalah contoh dalam beberapa bahasa mirip C dari deklarasi fungsi:

void foo(int param) {  // line 1
  param += 1;
}

Dan inilah contoh memanggil fungsi ini:

void bar() {
  int arg = 1;  // line 2
  foo(arg);     // line 3
}

Dengan menggunakan contoh ini, saya ingin mendefinisikan beberapa bit penting dari terminologi:

  • fooadalah fungsi yang dideklarasikan pada baris 1 (Java bersikeras membuat semua metode fungsi, tetapi konsepnya sama tanpa kehilangan keumuman; C dan C ++ membuat perbedaan antara deklarasi dan definisi yang tidak akan saya bahas di sini)
  • paramadalah parameter formal untuk foo, juga dinyatakan pada baris 1
  • argadalah variabel , khususnya variabel lokal dari fungsi bar, dideklarasikan dan diinisialisasi pada baris 2
  • argjuga merupakan argumen untuk spesifik doa dari fooon line 3

Ada dua rangkaian konsep yang sangat penting untuk dibedakan di sini. Yang pertama adalah nilai versus variabel :

  • Sebuah nilai adalah hasil dari evaluasi ekspresi dalam bahasa. Misalnya, dalam barfungsi di atas, setelah baris int arg = 1;, ekspresi argmemiliki nilai 1 .
  • Sebuah variabel adalah wadah untuk nilai-nilai . Variabel dapat berubah-ubah (ini adalah default di sebagian besar bahasa mirip-C), hanya-baca (misalnya dideklarasikan menggunakan Java finalatau C # 'sreadonly ) atau sangat tidak dapat diubah (misalnya menggunakan C ++' s const).

Sepasang konsep penting lainnya untuk dibedakan adalah parameter versus argumen :

  • Sebuah parameter (juga disebut parameter formal ) adalah variabel yang harus dipasok oleh pemanggil saat memanggil fungsi.
  • Sebuah argumen adalah nilai yang diberikan oleh pemanggil fungsi untuk memenuhi parameter formal tertentu dari fungsi yang

Panggil berdasarkan nilai

Dalam panggilan dengan nilai , parameter formal fungsi adalah variabel yang baru dibuat untuk pemanggilan fungsi, dan yang diinisialisasi dengan nilai argumennya.

Ini bekerja persis dengan cara yang sama seperti jenis variabel lainnya diinisialisasi dengan nilai. Sebagai contoh:

int arg = 1;
int another_variable = arg;

Di sini argdan another_variablemerupakan variabel yang sepenuhnya independen - nilainya dapat berubah secara independen satu sama lain. Namun, pada titik di mana another_variabledinyatakan, diinisialisasi untuk memegang nilai yang sama dengan yang argmemegang - yaitu 1.

Karena mereka adalah variabel independen, perubahan another_variabletidak mempengaruhi arg:

int arg = 1;
int another_variable = arg;
another_variable = 2;

assert arg == 1; // true
assert another_variable == 2; // true

Ini persis sama dengan hubungan antara argdan paramdalam contoh kita di atas, yang akan saya ulangi di sini untuk simetri:

void foo(int param) {
  param += 1;
}

void bar() {
  int arg = 1;
  foo(arg);
}

Persis seperti kita telah menulis kode dengan cara ini:

// entering function "bar" here
int arg = 1;
// entering function "foo" here
int param = arg;
param += 1;
// exiting function "foo" here
// exiting function "bar" here

Yaitu, karakteristik yang menentukan dari apa yang disebut dengan nilai berarti bahwa callee ( foodalam hal ini) menerima nilai sebagai argumen, tetapi memiliki variabel tersendiri untuk nilai-nilai tersebut dari variabel pemanggil (bar dalam kasus ini).

Kembali ke metafora saya di atas, jika saya bardan Anda foo, ketika saya memanggil Anda, saya menyerahkan selembar kertas dengan nilai tertulis di atasnya. Anda menyebut selembar kertas itu param. Nilai itu adalah salinan dari nilai yang saya tulis di notebook saya (variabel lokal saya), dalam variabel yang saya panggil arg.

(Sebagai tambahan: tergantung pada perangkat keras dan sistem operasi, ada berbagai konvensi panggilan tentang bagaimana Anda memanggil satu fungsi dari yang lain. Konvensi panggilan seperti kita memutuskan apakah saya menulis nilai pada selembar kertas saya dan kemudian menyerahkannya kepada Anda , atau jika Anda memiliki selembar kertas yang saya tuliskan, atau jika saya menulisnya di dinding di depan kami berdua. Ini juga merupakan subjek yang menarik, tetapi jauh di luar cakupan jawaban yang sudah lama ini.)

Panggil dengan referensi

Dalam panggilan dengan referensi , parameter formal fungsi hanyalah nama baru untuk variabel yang sama dengan yang diberikan pemanggil sebagai argumen.

Kembali ke contoh kami di atas, itu setara dengan:

// 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

Karena paramhanya nama lain untuk arg- yaitu, mereka adalah variabel yang sama , perubahan paramtercermin dalamarg . Ini adalah cara mendasar di mana panggilan dengan referensi berbeda dari panggilan dengan nilai.

Sangat sedikit bahasa yang mendukung panggilan dengan referensi, tetapi C ++ dapat melakukannya seperti ini:

void foo(int& param) {
  param += 1;
}

void bar() {
  int arg = 1;
  foo(arg);
}

Dalam hal ini, paramtidak hanya harus sama nilai seperti arg, itu benar-benar adalah arg (hanya dengan nama yang berbeda) dan bardapat mengamati bahwa argtelah bertambah.

Perhatikan bahwa ini bukan cara Java, JavaScript, C, Objective-C, Python, atau hampir semua bahasa populer lainnya saat ini. Ini berarti bahwa bahasa-bahasa itu bukan panggilan dengan referensi, mereka adalah panggilan dengan nilai.

Tambahan: panggilan dengan berbagi objek

Jika yang Anda miliki adalah panggilan dengan nilai , tetapi nilai sebenarnya adalah tipe referensi atau tipe pointer , maka "nilai" itu sendiri tidak terlalu menarik (misalnya dalam C itu hanya bilangan bulat dari ukuran platform-spesifik) - apa menarik adalah nilai apa yang ditunjukkan .

Jika apa yang ditunjuk oleh tipe referensi (yaitu, pointer) dapat diubah maka efek yang menarik mungkin terjadi: Anda dapat memodifikasi nilai menunjuk-ke, dan penelepon dapat mengamati perubahan pada nilai menunjuk-ke, meskipun penelepon tidak dapat mengamati perubahan pada pointer itu sendiri.

Untuk meminjam analogi URL lagi, fakta bahwa saya memberi Anda salinannya URL ke situs web tidak terlalu menarik jika hal yang kami berdua pedulikan adalah situs web, bukan URL. Fakta bahwa Anda menulis salinan URL tidak memengaruhi salinan URL saya bukanlah hal yang kami pedulikan (dan pada kenyataannya, dalam bahasa seperti Java dan Python "URL", atau nilai jenis referensi, dapat tidak dapat dimodifikasi sama sekali, hanya hal yang ditunjukkan olehnya yang dapat).

Barbara Liskov, ketika dia menemukan bahasa pemrograman CLU (yang memiliki semantik ini), menyadari bahwa istilah yang ada "panggilan berdasarkan nilai" dan "panggilan dengan referensi" tidak terlalu berguna untuk menggambarkan semantik bahasa baru ini. Jadi dia menemukan istilah baru: panggilan dengan berbagi objek .

Ketika membahas bahasa yang secara teknis disebut oleh nilai, tetapi di mana jenis yang umum digunakan adalah tipe referensi atau penunjuk (yaitu: hampir setiap bahasa pemrograman imperatif, berorientasi objek, atau multi-paradigma modern), saya merasa jauh lebih membingungkan untuk hanya menghindari berbicara tentang panggilan berdasarkan nilai atau panggilan dengan referensi . Stick untuk memanggil dengan berbagi objek (atau hanya memanggil dengan objek ) dan tidak ada yang akan bingung. :-)

Daniel Pryden
sumber
Dijelaskan Lebih Baik: Ada dua rangkaian konsep yang sangat penting untuk dibedakan di sini. The first is value versus variable. The other important pair of concepts to distinguish is parameter versus argument:
SK Venkat
2
Jawaban yang sangat bagus. Saya pikir saya akan menambahkan bahwa tidak ada penyimpanan baru yang perlu dibuat secara referensi. Nama parameter merujuk penyimpanan asli (memori). Terima kasih
drlolly
1
Jawaban terbaik IMO
Rafael Eyng
59

Sebelum memahami 2 istilah, Anda HARUS memahami yang berikut. Setiap benda, memiliki 2 hal yang dapat membuatnya dibedakan.

  • Nilainya
  • Alamatnya

Jadi, jika Anda katakan employee.name = "John"

ketahuilah bahwa ada 2 hal tentang name. Nilainya yang "John"dan juga lokasi di memori yang beberapa nomor heksadesimal mungkin seperti ini: 0x7fd5d258dd00.

Bergantung pada arsitektur bahasa atau jenis (kelas, struct, dll.) Objek Anda, Anda dapat mentransfer "John"atau0x7fd5d258dd00

Passing "John"dikenal sebagai passing oleh nilai. Passing 0x7fd5d258dd00dikenal sebagai passing oleh referensi. Siapa pun yang menunjuk ke lokasi memori ini akan memiliki akses ke nilai "John".

Untuk lebih lanjut tentang ini, saya sarankan Anda untuk membaca tentang dereferencing pointer dan juga mengapa memilih struct (tipe nilai) dari kelas (tipe referensi)

Madu
sumber
3
Itulah yang saya cari, sebenarnya orang harus mencari konsep bukan hanya penjelasannya, acungan jempol bro.
Haisum Usman
Java selalu melewati nilai. Melewati referensi ke objek di java dianggap lewat nilai. Ini bertentangan dengan pernyataan Anda "Melewati 0x7fd5d258dd00 dikenal sebagai lewat referensi.".
chetan raina
53

Berikut ini sebuah contoh:

#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
}
ular sanca
sumber
1
Saya pikir ada satu masalah karena baris terakhir harus mencetak 0 bukan 2. Mohon beri tahu saya jika saya melewatkan sesuatu.
Taimoor Changaiz
@TaimoorChangaiz; Yang "baris terakhir"? Omong-omong, jika Anda dapat menggunakan IRC, silakan datang ke ## pemrograman di Freenode. Akan jauh lebih mudah untuk menjelaskan hal-hal di sana. Nick saya ada "pyon".
pyon
1
@ EduardoLeón by_val (y); std :: cout << y << std :: endl; // mencetak 2
Taimoor Changaiz
5
@TaimoorChangaiz: Mengapa tidak mencetak 2? ytelah diatur ke 2 oleh baris sebelumnya. Mengapa kembali ke 0?
pyon
@ EduardoLeón my bad. ya kamu benar. Terima kasih atas koreksi
Taimoor Changaiz
28

Cara paling sederhana untuk mendapatkan ini adalah pada file Excel. Katakanlah misalnya Anda memiliki dua angka, 5 dan 2 dalam sel A1 dan B1 yang sesuai, dan Anda ingin menemukan jumlah mereka dalam sel ketiga, katakanlah A2. Anda dapat melakukan ini dengan dua cara.

  • Baik dengan mengirimkan nilainya ke sel A2 dengan mengetik = 5 + 2 ke dalam sel ini. Dalam hal ini, jika nilai sel A1 atau B1 berubah, jumlah dalam A2 tetap sama.

  • Atau dengan melewatkan "referensi" dari sel A1 dan B1 ke sel A2 dengan mengetik = A1 + B1 . Dalam hal ini, jika nilai sel A1 atau B1 berubah, jumlah dalam A2 juga berubah.

Dari pada Skourtan
sumber
Ini adalah contoh paling sederhana dan terbaik di antara semua jawaban lainnya.
Amit Ray
18

Ketika melewati oleh ref Anda pada dasarnya melewatkan pointer ke variabel. Lewati nilai Anda melewati salinan variabel. Dalam penggunaan dasar ini biasanya berarti perubahan ref by to variabel akan terlihat sebagai metode pemanggilan dan nilai by pass yang tidak biasa.

Craig
sumber
12

Pass by value mengirimkan COPY dari data yang disimpan dalam variabel yang Anda tentukan, pass by reference mengirimkan tautan langsung ke variabel itu sendiri. Jadi, jika Anda melewatkan variabel dengan referensi dan kemudian mengubah variabel di dalam blok tempat Anda meneruskannya, variabel asli akan berubah. Jika Anda hanya memberikan nilai, variabel asli tidak akan dapat diubah oleh blok yang Anda berikan, tetapi Anda akan mendapatkan salinan apa pun yang ada pada saat panggilan berlangsung.

MetaGuru
sumber
7

Pass by value - Fungsi menyalin variabel dan bekerja dengan salinan (sehingga tidak mengubah apa pun di variabel asli)

Lulus dengan referensi - Fungsi ini menggunakan variabel asli, jika Anda mengubah variabel di fungsi lain, itu berubah dalam variabel asli juga.

Contoh (salin dan gunakan / coba sendiri dan lihat):

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

Tetap sederhana, intip. Dinding teks bisa menjadi kebiasaan buruk.

pengguna326964
sumber
Ini sangat membantu dalam memahami apakah nilai parameter diubah atau tidak, terima kasih!
Kevin Zhao
5

Perbedaan utama di antara mereka adalah bahwa variabel tipe-nilai menyimpan nilai, jadi menentukan variabel tipe-nilai dalam pemanggilan metode meneruskan salinan nilai variabel itu ke metode. Variabel tipe referensi menyimpan referensi ke objek, jadi tentukan variabel tipe referensi sebagai argumen yang meneruskan metode salinan referensi aktual yang merujuk ke objek. Meskipun referensi itu sendiri dilewatkan oleh nilai, metode ini masih dapat menggunakan referensi yang diterimanya untuk berinteraksi dengan - dan mungkin memodifikasi - objek asli. Demikian pula, ketika mengembalikan informasi dari suatu metode melalui pernyataan kembali, metode mengembalikan salinan nilai yang disimpan dalam variabel tipe nilai atau salinan referensi yang disimpan dalam variabel tipe referensi. Ketika referensi dikembalikan, metode pemanggilan dapat menggunakan referensi itu untuk berinteraksi dengan objek yang direferensikan. Begitu,

Dalam c #, untuk mengirimkan variabel dengan referensi sehingga metode yang dipanggil dapat memodifikasi variabel, C # menyediakan kata kunci ref dan out. Menerapkan kata kunci ref ke deklarasi parameter memungkinkan Anda untuk mengirimkan variabel ke metode dengan referensi — metode yang dipanggil akan dapat memodifikasi variabel asli di pemanggil. Kata kunci ref digunakan untuk variabel yang sudah diinisialisasi dalam metode panggilan. Biasanya, ketika pemanggilan metode berisi variabel yang tidak diinisialisasi sebagai argumen, kompiler menghasilkan kesalahan. Sebelum parameter dengan kata kunci keluar menciptakan parameter output. Ini menunjukkan kepada kompiler bahwa argumen akan diteruskan ke metode yang dipanggil dengan referensi dan bahwa metode yang dipanggil akan memberikan nilai ke variabel asli di pemanggil. Jika metode ini tidak menetapkan nilai ke parameter output di setiap jalur eksekusi yang mungkin, kompiler menghasilkan kesalahan. Ini juga mencegah kompilator menghasilkan pesan kesalahan untuk variabel tidak diinisialisasi yang dilewatkan sebagai argumen ke metode. Suatu metode dapat mengembalikan hanya satu nilai ke pemanggilnya melalui pernyataan pengembalian, tetapi dapat mengembalikan banyak nilai dengan menetapkan beberapa parameter keluaran (ref dan / atau keluar).

lihat diskusi # c dan contoh di sini tautan teks

Tina Endresen
sumber
3

Contoh:

class Dog 
{ 
public:
    barkAt( const std::string& pOtherDog ); // const reference
    barkAt( std::string pOtherDog ); // value
};

const &umumnya yang terbaik. Anda tidak dikenakan hukuman konstruksi dan penghancuran. Jika referensi bukan const antarmuka Anda menyarankan bahwa itu akan mengubah data yang dikirimkan.

Deduplicator
sumber
2

Singkatnya, Lulus oleh nilai adalah APA itu dan disahkan oleh referensi MANA itu.

Jika nilai Anda adalah VAR1 = "Happy Guy!", Anda hanya akan melihat "Happy Guy!". Jika VAR1 berubah menjadi "Happy Gal!", Anda tidak akan tahu itu. Jika lulus dengan referensi, dan VAR1 berubah, Anda akan melakukannya.

Raksasa
sumber
2

Jika Anda tidak ingin mengubah nilai variabel asli setelah meneruskannya ke fungsi, fungsi tersebut harus dibangun dengan parameter " pass by value ".

Maka fungsi HANYA akan memiliki nilai tetapi bukan alamat variabel yang diteruskan. Tanpa alamat variabel, kode di dalam fungsi tidak dapat mengubah nilai variabel seperti yang terlihat dari luar fungsi.

Tetapi jika Anda ingin memberikan fungsi kemampuan untuk mengubah nilai variabel seperti yang terlihat dari luar, Anda perlu menggunakan referensi lewat . Karena nilai dan alamat (referensi) dilewatkan dan tersedia di dalam fungsi.

Stanley
sumber
1

pass by value berarti cara meneruskan nilai ke suatu fungsi dengan memanfaatkan argumen. dalam pass by value kita menyalin data yang disimpan dalam variabel yang kita tentukan dan itu lebih lambat daripada pass by reference karena data disalin. dari kami membuat perubahan dalam data yang disalin data asli tidak terpengaruh. dan melalui pass by refernce atau pass by address kami mengirimkan tautan langsung ke variabel itu sendiri. atau melewatkan pointer ke suatu variabel. lebih cepat karena lebih sedikit waktu yang dikonsumsi

abhinisha thakur
sumber
0

Berikut adalah contoh yang menunjukkan perbedaan antara nilai by pass - nilai pointer - referensi :

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
}

Metode “passing by reference” memiliki batasan penting . Jika suatu parameter dinyatakan sebagai lulus oleh referensi (sehingga didahului oleh tanda &) parameter aktualnya yang sesuai harus berupa variabel .

Parameter aktual yang merujuk pada parameter formal "lulus oleh nilai" dapat berupa ekspresi secara umum, sehingga diizinkan untuk menggunakan tidak hanya variabel tetapi juga hasil doa fungsi literal atau bahkan fungsi.

Fungsi tidak dapat menempatkan nilai pada sesuatu selain variabel. Itu tidak bisa menetapkan nilai baru ke literal atau memaksa ekspresi untuk mengubah hasilnya.

PS: Anda juga dapat memeriksa jawaban Dylan Beattie di utas saat ini yang menjelaskannya dengan kata-kata sederhana.

BugShotGG
sumber
Anda menyatakan "jika suatu parameter dinyatakan [sebagai referensi] parameter aktualnya yang sesuai harus berupa variabel", tetapi itu tidak benar secara umum. Jika referensi terikat untuk sementara (seperti nilai balik fungsi), masa pakai diperpanjang untuk mencocokkan referensi. Lihat di sini untuk detailnya.
Chris Hunt