Apa perbedaan antara variabel pointer dan variabel referensi di C ++?

Saya tahu bahwa tautan adalah gula sintaksis, sehingga kodenya lebih mudah dibaca dan ditulis.

Tetapi apa perbedaannya?


Ringkasan jawaban dan tautan di bawah ini:

  1. Pointer dapat dipindahkan beberapa kali, sementara tautan tidak dapat dipindahkan setelah mengikat.
  2. Pointer tidak dapat menunjuk ke mana saja ( NULL ), sedangkan tautan selalu merujuk ke objek.
  3. Anda tidak dapat menggunakan alamat tautan, seperti yang Anda bisa, petunjuk.
  4. Tidak ada "aritmatika referensi" (tetapi Anda dapat mengambil alamat objek yang menjadi titik tautan dan melakukan aritmatika pointer di atasnya, seperti pada + 5 ).

Untuk menjernihkan kesalahan:

Standar C ++ sangat berhati-hati untuk tidak menentukan bagaimana kompiler dapat mengimplementasikan referensi, tetapi setiap kompiler C ++ mengimplementasikan referensi sebagai pointer. Yaitu, deklarasi seperti:

 int  = i; 

jika tidak sepenuhnya dioptimalkan , alokasikan jumlah memori yang sama dengan pointer, dan tempatkan alamat i dalam repositori ini.

Dengan demikian, penunjuk dan tautan menggunakan jumlah memori yang sama.

Sebagai aturan umum,

  • Gunakan referensi dalam parameter fungsi dan tipe kembali untuk menyediakan antarmuka yang berguna dan mendokumentasikan diri.
  • Gunakan pointer untuk mengimplementasikan algoritma dan struktur data.

Bacaan menarik:

2802
11 сент. set prakash 11 September 2008-09-11 23:03 '08 pada 11:03 malam 2008-09-11 23:03
@ 37 jawaban
  • 1
  • 2
  • Pointer dapat dipindahkan:

     int x = 5; int y = 6; int *p; p =  p =  *p = 10; assert(x == 5); assert(y == 10); 

    Tautan tidak dapat dan harus ditetapkan selama inisialisasi:

     int x = 5; int y = 6; int  = x; 
  • Pointer memiliki alamatnya sendiri dan ukuran memori dalam stack (4 byte pada x86), sedangkan tautan memiliki alamat memori yang sama (dengan variabel asli), tetapi juga membutuhkan ruang pada stack. Karena tautan memiliki alamat yang sama dengan variabel asli, aman untuk menganggap tautan sebagai nama yang berbeda untuk variabel yang sama. Catatan Pointer menunjuk apa yang mungkin ada di stack atau di heap. Tautan yang sama. Klaim saya dalam pernyataan ini bukanlah bahwa pointer harus mengarah ke stack. Pointer hanyalah variabel yang berisi alamat memori. Variabel ini ada di tumpukan. Karena tautan memiliki ruang tumpukan sendiri, dan karena alamatnya sama dengan variabel yang dimaksud. Baca lebih lanjut tentang tumpukan vs tumpukan . Ini berarti ada alamat tautan asli yang tidak akan dikompilasi oleh kompiler.

     int x = 0; int  = x; int *p =  int *p2 =  assert(p == p2); 
  • Anda mungkin memiliki pointer ke pointer ke pointer yang menawarkan tingkat tipuan tambahan. Sedangkan tautan hanya menawarkan satu tingkat tipuan.

     int x = 0; int y = 0; int *p =  int *q =  int **pp =  pp =  = q **pp = 4; assert(y == 4); assert(x == 0); 
  • Pointer dapat diberikan nullptr secara >nullptr . Demikian pula, jika Anda berusaha cukup keras, Anda dapat memiliki tautan ke sebuah pointer, dan kemudian tautan itu mungkin mengandung nullptr .

     int *p = nullptr; int  = nullptr; <--- compiling error int  = *p; <--- likely no compiling error, especially if the nullptr is hidden behind a function call, yet it refers to a non-existent int at address 0 
  • Pointer dapat beralih pada array, Anda dapat menggunakan ++ untuk pergi ke elemen berikutnya yang ditunjuk oleh pointer, dan + 4 untuk pergi ke item ke-5. Ini terlepas dari ukuran apa yang ditunjukkan objek.

  • Pointer harus didereferensi dengan menggunakan * untuk mengakses alamat memori yang ditunjuknya, sementara tautan dapat digunakan secara >-> untuk mengaksesnya, sedangkan tautan menggunakan . .

  • Pointer adalah variabel yang berisi alamat memori. Terlepas dari bagaimana tautan ini dijalankan, tautan tersebut memiliki alamat memori yang sama dengan elemen yang dimaksud.

  • Tautan tidak dapat diisi ke dalam array, sementara pointer dapat (ditentukan oleh pengguna @litb)

  • Referensi ke Const dapat dikaitkan dengan sementara. Pointer tidak dapat (bukan tanpa ketidak >

     const int  = int(12); //legal C++ int *y =  //illegal to dereference a temporary. 

    Ini membuat const> lebih aman untuk digunakan dalam daftar argumen, dll.

1469
11 сент. Jawabannya diberikan oleh Brian R. Bondy 11 sep. 2008-09-11 23:08 '08 pada jam 11:08 2008-09-11 23:08

Apa itu referensi C ++ (untuk programmer C)

Tautan dapat dianggap sebagai pointer konstan (jangan dikacaukan dengan pointer ke nilai konstan!) Dengan tipuan otomatis, mis. kompiler akan menggunakan operator * untuk Anda.

Semua tautan harus diinisialisasi dengan nilai bukan nol, jika tidak kompilasi akan gagal. Tidak mungkin untuk mendapatkan alamat tautan - sebaliknya, operator alamat mengembalikan alamat nilai referensi - dan tidak mungkin untuk melakukan aritmatika tautan.

Pemrogram C mungkin tidak menyukai referensi C ++, karena tidak akan lebih jelas jika tidak >

Pemrogram C ++ mungkin tidak menyukai penggunaan pointer, karena mereka dianggap tidak aman, meskipun tautan sebenarnya tidak lebih aman daripada pointer biasa, kecuali untuk kasus yang paling sepele - mereka tidak memiliki kenyamanan tipuan otomatis dan membawa konotasi semantik yang berbeda.

Pertimbangkan pernyataan berikut dari C ++ Pertanyaan yang Sering Diajukan :

Bahkan jika tautan tersebut sering dilakukan menggunakan alamat dalam bahasa dasar assembler, harap jangan menganggap tautan itu sebagai penunjuk yang terlihat lucu pada suatu objek. Tautan adalah objek. itu bukan penunjuk ke objek, atau salinan dari suatu objek. Ini adalah sebuah objek.

Tetapi jika tautan itu benar-benar objek, bagaimana mungkin ada tautan yang rusak? Dalam bahasa yang tidak dikelola, tautan tidak mungkin menjadi "lebih aman" dari pada petunjuk - sebagai aturan, ini bukan cara yang bisa diandalkan untuk mengaitkan nilai-nilai batas ruang lingkup secara andal!

Mengapa saya menemukan tautan yang bermanfaat di C ++

Berdasarkan latar belakang C, referensi C ++ mungkin terlihat seperti konsep yang agak konyol, tetapi Anda tetap harus menggunakannya alih-alih petunjuk yang memungkinkan: tipuan otomatis nyaman, dan tautan menjadi sangat berguna saat bekerja dengan RAII - tetapi bukan karena setiap manfaat keamanan yang dirasakan, tetapi lebih karena fakta bahwa mereka membuat surat kode idiomatik kurang nyaman.

RAII adalah salah satu konsep sentral dari C ++, tetapi berinteraksi secara nontrivial dengan menyalin semantik. Melewati objek dengan referensi memungkinkan Anda menghindari masalah ini, karena penyalinan tidak dilakukan. Jika tautan tidak ada dalam bahasa ini, Anda harus menggunakan pointer, yang lebih rumit untuk digunakan, sehingga me>

324
28 февр. Balas diberikan oleh Christoph pada 28 Februari 2009-02-28 00:26 '09 pada 0:26 AM 2009-02-28 00:26

Jika Anda ingin benar-benar bertele-tele, ada satu hal yang dapat Anda lakukan dengan tautan yang tidak dapat Anda lakukan dengan sebuah penunjuk: memperpanjang umur objek sementara. Di C ++, jika Anda mengikat referensi const ke objek sementara, masa pakai objek ini menjadi masa pakai tautan.

 std::string s1 = "123"; std::string s2 = "456"; std::string s3_copy = s1 + s2; const std::string s3_reference = s1 + s2; 

Dalam contoh ini, s3_copy menyalin objek sementara yang merupakan hasil penggabungan. Sementara s3_reference pada dasarnya menjadi objek sementara. Ini benar-benar tautan ke objek sementara, yang sekarang memiliki masa pakai yang sama dengan tautan.

Jika Anda mencoba ini tanpa const , seharusnya tidak dikompilasi. Anda tidak dapat mengikat tautan tidak konstan ke objek sementara, dan Anda tidak dapat menerima alamatnya dalam hal ini.

167
12 сент. Balas Matt Price 12 sept. 2008-09-12 00:43 '08 pada 0:43 2008-09-12 00:43

Bertentangan dengan kepercayaan populer, dimungkinkan untuk memiliki tautan NULL.

 int * p = NULL; int  r = *p; r = 1; // crash! (if you're lucky) 

Tentu saja, ini jauh lebih sulit dilakukan dengan tautan, tetapi jika Anda mengatasinya, Anda akan merobek rambut Anda, mencoba menemukannya. Tautan C ++ secara inheren tidak aman!

Secara teknis, ini adalah referensi yang tidak valid , bukan referensi nol. C ++ tidak mendukung tautan nol sebagai konsep, seperti yang dapat Anda temukan dalam bahasa lain. Ada tautan tidak valid lainnya. Setiap tautan yang tidak valid meningkatkan rentang perilaku yang tidak terdefinisi , serta penggunaan penunjuk yang tidak valid.

Kesalahan sebenarnya adalah dalam mendereferensi pointer NULL sebelum menetapkan tautan. Tetapi saya tidak tahu ada kompiler yang akan menghasilkan kesalahan dalam kondisi ini - kesalahan didistribusikan ke titik lebih lanjut dalam kode. Ini membuat masalah ini sangat berbahaya. Dalam kebanyakan kasus, jika Anda memainkan pointer NULL, Anda bersumpah tepat di tempat ini, dan itu tidak memerlukan banyak debugging untuk mengetahuinya.

Contoh saya di atas pendek dan dibuat-buat. Ini adalah contoh yang lebih realistis.

 class MyClass { ... virtual void DoSomething(int,int,int,int,int); }; void Foo(const MyClass  bar) { ... bar.DoSomething(i1,i2,i3,i4,i5); // crash occurs here due to memory access violation - obvious why? } MyClass * GetInstance() { if (somecondition) return NULL; ... } MyClass * p = GetInstance(); Foo(*p); 

Saya ingin mengu>if(> , tetapi kompiler dapat mengoptimalkan pernyataan dari keberadaan! Referensi yang valid tidak pernah bisa NULL, jadi membandingkan selalu salah dari representasi kompiler, dan Anda dapat dengan bebas mengecualikan klausa if sebagai kode mati - ini adalah inti dari perilaku undefined.

Cara yang benar untuk menghindari masalah adalah dengan menghindari dereferensi pointer nol untuk membuat referensi. Ini adalah cara otomatis untuk melakukan ini.

 template<typename T> T deref(T* p) { if (p == NULL) throw std::invalid_argument(std::string("NULL reference")); return *p; } MyClass * p = GetInstance(); Foo(deref(p)); 

Untuk melihat lebih lama masalah ini bagi seseorang yang memiliki keterampilan menulis terbaik, lihat Referensi Null dari Jim Hyslop dan Herb Sutter.

Untuk contoh lain tentang bahaya penereferensian pointer nol, lihat Menampilkan perilaku yang tidak terdefinisi saat mencoba mentransfer kode ke platform Raymond Chen lainnya.

114
12 сент. Balas diberikan oleh Mark Ransom 12 Sep 2008-09-12 00:06 '08 pada 0:06 2008-09-12 00:06

Selain gula sintaksis, tautannya adalah pointer const (bukan pointer ke const ). Anda harus menetapkan apa yang dirujuk saat Anda mendeklarasikan variabel referensi, dan Anda tidak dapat mengubahnya nanti.

Perbarui: sekarang saya memikirkannya lagi, ada perbedaan penting.

Pointer konstanta target dapat diganti dengan mengambil alamatnya dan menggunakan konstanta konstanta.

Tautan target tidak dapat diganti dengan cara apa pun di bawah UB.

Ini harus memungkinkan kompiler membuat optimasi besar dengan referensi.

111
11 сент. jawabannya diberikan Arkadiy 11 September. 2008-09-11 23:07 '08 pada 11:07 malam 2008-09-11 23:07

Anda lupa bagian terpenting:

akses anggota dengan pointer menggunakan ->
akses anggota dengan penggunaan tautan .

foo.bar jelas lebih unggul daripada foo->bar dengan cara yang sama dengan vi jelas lebih unggul dari Emacs : -)

105
12 сент. Jawaban diberikan oleh Orion Edwards 12 Sep. 2008-09-12 01:10 '08 pada 1:10 pagi 2008-09-12 01:10

Sebenarnya, tautannya tidak terlihat seperti penunjuk.

Kompiler menyimpan "referensi" ke variabel, mengaitkan nama dengan alamat memori; bahwa tugasnya adalah menerjemahkan nama variabel apa pun ke alamat memori saat kompilasi.

Saat Anda membuat tautan, Anda memberi tahu kompiler bahwa Anda menetapkan nama variabel pointer yang berbeda; mengapa tautan tidak dapat "menunjuk ke nol", karena suatu variabel tidak bisa, dan tidak bisa.

Pointer adalah variabel; mereka berisi alamat beberapa variabel lain atau mungkin nol. Penting bahwa pointer memiliki nilai, dan tautan hanya memiliki variabel yang dirujuk.

Sekarang untuk beberapa penjelasan tentang kode asli:

 int a = 0; int b = a; 

Di sini Anda tidak membuat variabel lain yang menunjuk ke; Anda cukup menambahkan nama lain ke isi memori yang mengandung nilai a . Memori ini sekarang memiliki dua nama: a dan b , dan dapat diselesaikan menggunakan nama apa pun.

 void increment(int n) { n = n + 1; } int a; increment(a); 

Ketika suatu fungsi dipanggil, kompiler biasanya menghasilkan ruang memori untuk argumen yang akan disalin. Tanda tangan fungsional mendefinisikan ruang yang akan dibuat dan memberikan nama yang akan digunakan untuk ruang ini. Mendeklarasikan parameter sebagai referensi hanya memberi tahu kompiler untuk menggunakan spasi dengan variabel input variabel alih-alih mengalokasikan ruang memori baru selama pemanggilan metode. Mungkin aneh untuk mengatakan bahwa fungsi Anda akan secara >

Sekarang mungkin ada saat-saat ketika kompiler Anda mungkin tidak tahu referensi ketika mengkompilasi, misalnya, ketika menggunakan variabel extern. Dengan demikian, tautannya mungkin atau mungkin tidak diterapkan sebagai penunjuk dalam kode dasar. Tetapi dalam contoh yang saya berikan kepada Anda, kemungkinan besar tidak akan diimplementasikan dengan pointer.

63
19 сент. jawabannya diberikan oleh Vincent Robert 19 sept. 2008-09-19 15:23 '08 pada 15:23 2008-09-19 15:23

Tautan sangat mirip dengan pointer, tetapi mereka secara khusus dibuat untuk membantu mengoptimalkan kompiler.

  • Referensi dirancang sedemikian rupa sehingga kompiler sangat menyederhanakan alias pelacakan referensi, yang merupakan variabel. Dua fitur penting sangat penting: tidak ada "aritmatika referensi" dan tidak ada penugasan kembali referensi. Mereka memungkinkan kompiler untuk mencari tahu tautan mana yang mengandung alias, variabel mana pada waktu kompilasi.
  • Tautan dapat merujuk ke variabel yang tidak memiliki alamat memori, misalnya, yang dipilih kompilator untuk masuk ke register. Jika Anda mengambil alamat variabel lokal, kompiler sangat sulit untuk dimasukkan ke dalam register.

Sebagai contoh:

 void maybeModify(int x); // may modify x in some way void hurtTheCompilersOptimizer(short size, int array[]) { // This function is designed to do something particularly troublesome // for optimizers. It will constantly call maybeModify on array[0] while // adding array[1] to array[2]..array[size-1]. There no real reason to // do this, other than to demonstrate the power of references. for (int i = 2; i < (int)size; i++) { maybeModify(array[0]); array[i] += array[1]; } } 

Kompilator pengoptimal dapat memahami bahwa kita mengakses [0] dan [1] cukup banyak. Akan diinginkan untuk mengoptimalkan algoritme sehingga:

 void hurtTheCompilersOptimizer(short size, int array[]) { // Do the same thing as above, but instead of accessing array[1] // all the time, access it once and store the result in a register, // which is much faster to do arithmetic with. register int a0 = a[0]; register int a1 = a[1]; // access a[1] once for (int i = 2; i < (int)size; i++) { maybeModify(a0); // Give maybeModify a reference to a register array[i] += a1; // Use the saved register value over and over } a[0] = a0; // Store the modified a[0] back into the array } 

Untuk melakukan optimasi ini, perlu untuk membuktikan bahwa selama panggilan tidak ada yang dapat mengubah array [1]. Ini cukup mudah dilakukan. Saya setidaknya 2, jadi array [i] tidak pernah bisa merujuk ke array [1]. maybeModify () ditugaskan ke a0 sebagai referensi (aliasing array [0]). Karena tidak ada aritmatika "referensi", kompiler hanya perlu membuktikan bahwa itu mungkin. Modifikasi tidak pernah mendapatkan alamat x, dan ia membuktikan bahwa tidak ada yang mengubah array [1].

Itu juga harus membuktikan bahwa tidak mungkin panggilan selanjutnya dapat membaca / menulis [0], sementara kami memiliki salinan sementara dari registri dalam a0. Ini sering sepele untuk dibuktikan, karena dalam banyak kasus jelas bahwa referensi ini tidak pernah disimpan dalam struktur permanen, seperti turunan kelas.

Sekarang lakukan hal yang sama dengan pointer.

 void maybeModify(int* x); // May modify x in some way void hurtTheCompilersOptimizer(short size, int array[]) { // Same operation, only now with pointers, making the // optimization trickier. for (int i = 2; i < (int)size; i++) { maybeModify( array[i] += array[1]; } } 

Tingkah lakunya sama; hanya sekarang ini jauh lebih sulit untuk membuktikan bahwa, mungkin, Modifikasi tidak pernah memodifikasi array [1], karena kami sudah menunjukkan sebuah pointer; kucing keluar dari tas. Sekarang dia harus membuat bukti yang jauh lebih rumit: analisis statis mungkin memodifikasi untuk membuktikan bahwa dia tidak pernah menulis ke x + 1. Dia juga harus membuktikan bahwa dia tidak pernah menghi>

Kompiler modern menjadi lebih baik dan lebih baik dengan analisis statis, tetapi selalu menyenangkan untuk membantu mereka dan menggunakan tautan.

Tentu saja, dengan pengecualian optimisasi yang cerdik seperti itu, kompiler memang akan merujuk ke referensi dalam petunjuk jika diperlukan.

EDIT: Lima tahun setelah publikasi jawaban ini, saya menemukan perbedaan teknis yang sebenarnya, di mana tautan berbeda dari cara lain dalam memandang konsep pengalamatan yang sama. Tautan dapat mengubah masa objek sementara sedemikian rupa sehingga pointer tidak bisa.

 F createF(int argument); void extending() { const F ref = createF(5); std::cout << ref.getArgument() << std::endl; }; 

Biasanya pada akhir ekspresi, biasanya objek sementara dihancurkan, seperti yang dibuat dengan memanggil createF(5) . Namun, dengan menautkan objek ini dengan referensi, ref , C ++ akan memperpanjang umur objek sementara ini sampai ref melampaui.

62
01 сент. Jawabannya diberikan Cort Ammon 01 sep. 2013-09-01 06:44 '13 pada 6:44 2013-09-01 06:44

Tautan tidak akan pernah bisa NULL .

37
11 сент. Jawabannya diberikan oleh RichS pada 11 September 2008-09-11 23:12 '08 pada jam 11:12 2008-09-11 23:12

Meskipun kedua tautan dan petunjuk digunakan untuk secara tidak >

Pertimbangkan dua fragmen program ini. Yang pertama, kami menetapkan satu pointer ke yang lain:

 int ival = 1024, ival2 = 2048; int *pi =  *pi2 =  pi = pi2; // pi now points to ival2 

Setelah penugasan, ival, objek yang ditangani oleh pi tetap tidak berubah. Tugas mengubah nilai pi, menunjuk ke objek lain. Sekarang pertimbangkan program serupa yang menetapkan dua tautan:

 int  = ival,  = ival2; ri = ri2; // assigns ival2 to ival 

Tugas ini mengubah nilai ival, nilai yang dirujuk oleh ri, dan bukan tautan itu sendiri. После назначения две ссылки по-прежнему относятся к их исходным объектам, и значение этих объектов теперь тоже самое.

33
ответ дан Kunal Vyas 20 мая '11 в 22:26 2011-05-20 22:26