Pertanyaan Apa perbedaan antara variabel pointer dan variabel referensi dalam C ++?


Saya tahu referensi adalah gula sintaksis, sehingga kode lebih mudah dibaca dan ditulis.

Tetapi apa perbedaannya?


Ringkasan dari jawaban dan tautan di bawah ini:

  1. Penunjuk dapat diberikan kembali beberapa kali ketika referensi tidak dapat ditetapkan kembali setelah pengikatan.
  2. Pointer dapat menunjukkan tempat (NULL), sedangkan referensi selalu mengacu pada suatu objek.
  3. Anda tidak dapat mengambil alamat referensi seperti yang Anda bisa dengan pointer.
  4. Tidak ada "aritmatika referensi" (tetapi Anda dapat mengambil alamat objek yang ditunjuk oleh referensi dan melakukan aritmatika penunjuk di dalamnya seperti pada &obj + 5).

Untuk memperjelas kesalahpahaman:

Standar C ++ sangat berhati-hati untuk menghindari mendikte bagaimana kompiler bisa   mengimplementasikan referensi, tetapi setiap alat compiler C ++   referensi sebagai petunjuk. Yaitu, deklarasi seperti:

int &ri = i;

jika tidak dioptimalkan sepenuhnya, mengalokasikan jumlah penyimpanan yang sama   sebagai penunjuk, dan menempatkan alamat   dari i ke dalam penyimpanan itu.

Jadi, penunjuk dan referensi keduanya menggunakan jumlah memori yang sama.

Sebagai aturan umum,

  • Gunakan referensi dalam parameter fungsi dan jenis kembalian untuk menyediakan antarmuka yang bermanfaat dan mendokumentasikan diri.
  • Gunakan pointer untuk mengimplementasikan algoritma dan struktur data.

Bacaan menarik:


2617
2017-09-11 20:03


asal


Jawaban:


  1. Penunjuk dapat ditugaskan kembali:

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

    Referensi tidak bisa, dan harus ditetapkan saat inisialisasi:

    int x = 5;
    int y = 6;
    int &r = x;
    
  2. Penunjuk memiliki alamat dan ukuran memori sendiri pada stack (4 byte pada x86), sedangkan referensi berbagi alamat memori yang sama (dengan variabel asli) tetapi juga membutuhkan beberapa ruang pada stack. Karena referensi memiliki alamat yang sama dengan variabel asli itu sendiri, adalah aman untuk memikirkan referensi sebagai nama lain untuk variabel yang sama. Catatan: Apa yang bisa ditunjukkan pointer ke tumpukan atau tumpukan. Ditto referensi. Klaim saya dalam pernyataan ini bukanlah bahwa pointer harus menunjuk ke stack. Penunjuk hanya variabel yang menyimpan alamat memori. Variabel ini ada di tumpukan. Karena referensi memiliki ruang sendiri di stack, dan karena alamatnya sama dengan variabel yang dirujuknya. Lebih lanjut tentang stack vs heap. Ini menyiratkan bahwa ada alamat sebenarnya dari referensi yang tidak akan diberikan oleh kompiler kepada Anda.

    int x = 0;
    int &r = x;
    int *p = &x;
    int *p2 = &r;
    assert(p == p2);
    
  3. Anda dapat memiliki pointer ke pointer ke pointer menawarkan tingkat tambahan tipuan. Padahal referensi hanya menawarkan satu tingkat tipuan.

    int x = 0;
    int y = 0;
    int *p = &x;
    int *q = &y;
    int **pp = &p;
    pp = &q;//*pp = q
    **pp = 4;
    assert(y == 4);
    assert(x == 0);
    
  4. Pointer dapat ditugaskan nullptr langsung, sedangkan referensi tidak bisa. Jika Anda berusaha cukup keras, dan Anda tahu caranya, Anda dapat membuat alamat referensi nullptr. Demikian juga, jika Anda berusaha cukup keras Anda dapat memiliki referensi ke pointer, dan kemudian referensi itu dapat berisi nullptr.

    int *p = nullptr;
    int &r = nullptr; <--- compiling error
    int &r = *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
    
  5. Pointer dapat melakukan iterasi melalui array, yang dapat Anda gunakan ++untuk pergi ke item berikutnya yang menunjuk pointer, dan + 4 untuk menuju ke elemen ke-5. Ini tidak peduli apa ukuran objek adalah bahwa pointer menunjuk ke.

  6. Sebuah pointer harus dereferenced * untuk mengakses lokasi memori yang ditunjuknya, sedangkan referensi dapat digunakan secara langsung. Penunjuk ke kelas / struct menggunakan -> untuk mengakses anggota itu sedangkan referensi menggunakan ..

  7. Penunjuk adalah variabel yang menyimpan alamat memori. Terlepas dari bagaimana referensi diterapkan, referensi memiliki alamat memori yang sama dengan item yang dirujuknya.

  8. Referensi tidak dapat dimasukkan ke dalam array, sedangkan pointer dapat (Disebutkan oleh pengguna @litb)

  9. Referensi Const dapat terikat untuk sementara. Pointer tidak bisa (bukan tanpa tipuan):

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

    Ini membuat const& lebih aman untuk digunakan dalam daftar argumen dan sebagainya.


1369
2018-02-27 21:26



Apa itu referensi C ++ (untuk programmer C)

SEBUAH referensi dapat dianggap sebagai penunjuk konstan (jangan dikelirukan dengan pointer ke nilai konstan!) dengan indirection otomatis, yaitu compiler akan menerapkan * operator untuk Anda.

Semua referensi harus diinisialisasi dengan nilai non-null atau kompilasi akan gagal. Tidak mungkin untuk mendapatkan alamat referensi - operator alamat akan mengembalikan alamat dari nilai yang direferensikan - juga tidak mungkin untuk melakukan aritmatika pada referensi.

Pemrogram C mungkin tidak menyukai referensi C ++ karena tidak akan lagi jelas ketika terjadi ketidakjelasan atau jika argumen dilewatkan oleh nilai atau dengan penunjuk tanpa melihat tanda tangan fungsi.

Pemrogram C ++ mungkin tidak suka menggunakan pointer karena dianggap tidak aman - meskipun referensi tidak benar-benar lebih aman daripada pointer konstan kecuali dalam kasus yang paling sepele - kurangnya kenyamanan tipuan otomatis dan membawa konotasi semantik yang berbeda.

Pertimbangkan pernyataan berikut dari C ++ FAQ:

Meskipun referensi sering diimplementasikan menggunakan alamat di   bahasa assembly yang mendasari, silakan lakukan tidak memikirkan referensi sebagai   lucu mencari pointer ke suatu objek. Referensi aku s objek. ini   bukan penunjuk ke objek, atau salinan objek. Saya t aku s itu   obyek.

Tetapi jika referensi sangat adalah objeknya, bagaimana bisa ada referensi yang menggantung? Dalam bahasa yang tidak dikelola, tidak mungkin referensi menjadi 'lebih aman' daripada pointer - biasanya tidak ada cara untuk menggunakan nilai-nilai alias yang dapat dipercaya lintas batas!

Mengapa saya menganggap referensi C ++ berguna

Berasal dari latar belakang C, referensi C ++ mungkin terlihat seperti konsep yang agak konyol, tetapi orang harus tetap menggunakannya sebagai ganti pointer jika mungkin: Ketidaktepatan otomatis aku s nyaman, dan referensi menjadi sangat berguna ketika berhadapan dengan RAII - tetapi bukan karena keuntungan keamanan yang dirasakan, tetapi lebih karena mereka membuat penulisan kode idiomatik kurang canggung.

RAII adalah salah satu konsep sentral C ++, tetapi ia berinteraksi secara non-sepele dengan menyalin semantik. Melewati objek dengan referensi menghindari masalah ini karena tidak ada penyalinan yang terlibat. Jika referensi tidak ada dalam bahasa, Anda harus menggunakan pointer sebagai gantinya, yang lebih rumit untuk digunakan, sehingga melanggar prinsip desain bahasa bahwa solusi praktik terbaik seharusnya lebih mudah daripada alternatifnya.


301
2017-09-11 21:43



Jika Anda ingin benar-benar bertele-tele, ada satu hal yang dapat Anda lakukan dengan referensi yang tidak dapat Anda lakukan dengan penunjuk: memperpanjang umur objek sementara. Dalam C ++ jika Anda mengikat referensi const ke objek sementara, umur objek itu menjadi acuan seumur hidup.

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. Sedangkan s3_reference pada intinya menjadi objek sementara. Ini benar-benar referensi ke objek sementara yang sekarang memiliki masa hidup yang sama dengan referensi.

Jika Anda mencoba ini tanpa const itu harus gagal dikompilasi. Anda tidak dapat mengikat referensi non-const ke objek sementara, Anda juga tidak dapat mengambil alamatnya dalam hal ini.


151
2017-09-11 21:06



Bertentangan dengan pendapat populer, adalah mungkin untuk memiliki referensi yang NULL.

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

Memang, itu jauh lebih sulit untuk dilakukan dengan referensi - tetapi jika Anda mengelolanya, Anda akan merobek rambut Anda mencoba menemukannya. Referensi adalah tidak secara inheren aman di C ++!

Secara teknis ini adalah sebuah referensi tidak valid, bukan referensi nol. C ++ tidak mendukung referensi nol sebagai konsep yang mungkin Anda temukan dalam bahasa lain. Ada jenis referensi tidak valid lainnya juga. Apa saja referensi tidak valid meningkatkan momok perilaku tidak terdefinisi, seperti menggunakan penunjuk yang tidak valid.

Kesalahan sebenarnya ada di dereferencing penunjuk NULL, sebelum penugasan ke referensi. Tapi saya tidak mengetahui adanya kompiler yang akan menghasilkan kesalahan pada kondisi itu - kesalahan menyebar ke titik lebih jauh di dalam kode. Itulah yang membuat masalah ini begitu berbahaya. Sebagian besar waktu, jika Anda mengalihkan pointer NULL, Anda menabrak kanan di tempat itu dan tidak perlu banyak debugging untuk mengetahuinya.

Contoh saya di atas pendek dan dibuat-buat. Inilah contoh yang lebih nyata.

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 menegaskan kembali bahwa satu-satunya cara untuk mendapatkan referensi nol adalah melalui kode yang salah, dan setelah Anda memilikinya, Anda mendapatkan perilaku tidak terdefinisi. Saya t tak pernah masuk akal untuk memeriksa referensi nol; misalnya Anda bisa mencoba if(&bar==NULL)... tetapi kompilator mungkin mengoptimalkan pernyataan tidak ada! Referensi yang valid tidak boleh NULL jadi dari pandangan kompiler perbandingan selalu salah, dan bebas untuk menghilangkan if klausul sebagai kode mati - ini adalah inti dari perilaku tidak terdefinisi.

Cara yang tepat untuk tetap keluar dari masalah adalah menghindari dereferencing penunjuk NULL untuk membuat referensi. Inilah cara otomatis untuk mencapai 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 tampilan yang lebih lama pada masalah ini dari seseorang dengan kemampuan menulis yang lebih baik, lihat Null Referensi dari Jim Hyslop dan Herb Sutter.

Untuk contoh lain dari bahaya dereferencing pointer null lihat Mengekspos perilaku tidak terdefinisi saat mencoba kode port ke platform lain oleh Raymond Chen.


104
2017-09-11 22:10



Anda lupa bagian terpenting:

akses-anggota dengan menggunakan pointer -> 
akses anggota dengan menggunakan referensi .

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


96
2017-09-11 20:07



Selain gula sintaksis, referensi adalah a const penunjuk (tidak penunjuk ke a const). Anda harus menetapkan apa yang merujuk ketika Anda menyatakan variabel referensi, dan Anda tidak dapat mengubahnya nanti.

Perbarui: sekarang setelah saya memikirkannya lagi, ada perbedaan penting.

Target const pointer dapat diganti dengan mengambil alamatnya dan menggunakan const cast.

Target referensi tidak dapat diganti dengan cara apapun yang pendek dari UB.

Ini harus memungkinkan kompiler untuk melakukan lebih banyak optimasi pada referensi.


95
2017-09-19 12:23



Sebenarnya, referensi tidak benar-benar seperti penunjuk.

Compiler menyimpan "referensi" ke variabel, mengaitkan nama dengan alamat memori; itu tugasnya untuk menerjemahkan nama variabel ke alamat memori saat kompilasi.

Ketika Anda membuat referensi, Anda hanya memberitahu compiler yang Anda tetapkan nama lain ke variabel pointer; itu sebabnya referensi tidak bisa "menunjuk ke nol", karena variabel tidak bisa, dan tidak bisa.

Pointer adalah variabel; mereka mengandung alamat dari beberapa variabel lain, atau bisa null. Yang penting adalah bahwa pointer memiliki nilai, sementara referensi hanya memiliki variabel yang dirujuk.

Sekarang beberapa penjelasan tentang kode asli:

int a = 0;
int& b = a;

Di sini Anda tidak membuat variabel lain yang mengarah ke a; Anda hanya menambahkan nama lain ke konten memori yang memiliki nilai a. Memori ini sekarang memiliki dua nama, a dan b, dan itu bisa diatasi menggunakan salah satu nama.

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

int a;
increment(a);

Ketika memanggil fungsi, compiler biasanya menghasilkan ruang memori untuk argumen yang akan disalin. Tanda tangan fungsi mendefinisikan ruang yang harus dibuat dan memberikan nama yang harus digunakan untuk ruang-ruang ini. Mendeklarasikan parameter sebagai referensi hanya memberitahu compiler untuk menggunakan ruang memori variabel input daripada mengalokasikan ruang memori baru selama panggilan metode. Mungkin aneh untuk mengatakan bahwa fungsi Anda akan langsung memanipulasi variabel yang dideklarasikan dalam lingkup panggilan, tetapi ingat bahwa ketika mengeksekusi kode yang dikompilasi, tidak ada ruang lingkup lagi; hanya ada memori datar datar, dan kode fungsi Anda bisa memanipulasi variabel apa pun.

Sekarang mungkin ada beberapa kasus di mana kompiler Anda mungkin tidak dapat mengetahui referensi saat mengkompilasi, seperti ketika menggunakan variabel eksternal. Jadi referensi mungkin atau mungkin tidak diimplementasikan sebagai penunjuk dalam kode yang mendasari. Namun dalam contoh yang saya berikan kepada Anda, kemungkinan besar tidak akan diimplementasikan dengan penunjuk.


57
2017-09-01 03:44



Referensi sangat mirip dengan pointer, tetapi mereka secara khusus dibuat untuk membantu mengoptimalkan kompilator.

  • Referensi dirancang sedemikian rupa sehingga secara substansial lebih mudah bagi compiler untuk melacak referensi yang mana variabel mana. Dua fitur utama sangat penting: tidak ada "aritmatika referensi" dan tidak ada penugasan referensi. Ini memungkinkan compiler untuk mencari referensi mana alias variabel mana pada waktu kompilasi.
  • Referensi diizinkan untuk merujuk ke variabel yang tidak memiliki alamat memori, seperti yang dipilih oleh kompilator untuk dimasukkan ke dalam register. Jika Anda mengambil alamat dari variabel lokal, sangat sulit bagi compiler untuk memasukkannya 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's 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];
    }
}

Sebuah kompiler yang mengoptimalisasi mungkin menyadari bahwa kita mengakses [0] dan [1] cukup banyak. Akan sangat senang mengoptimalkan algoritme untuk:

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 membuat pengoptimalan seperti itu, perlu dibuktikan bahwa tidak ada yang bisa mengubah array [1] selama panggilan. Ini agak mudah dilakukan. i tidak pernah kurang dari 2, jadi array [i] tidak bisa merujuk ke array [1]. maybeModify () diberikan a0 sebagai referensi (aliasing array [0]). Karena tidak ada "referensi" aritmatika, kompiler hanya harus membuktikan bahwa mungkinModify tidak pernah mendapatkan alamat x, dan itu telah membuktikan bahwa tidak ada perubahan array [1].

Ini juga harus membuktikan bahwa tidak ada cara panggilan masa depan bisa membaca / menulis [0] sementara kami memiliki salinan daftar sementara di a0. Hal ini sering sepele untuk dibuktikan, karena dalam banyak kasus jelas bahwa referensi tidak pernah disimpan dalam struktur permanen seperti contoh 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[0]));
        array[i] += array[1];
    }
}

Perilaku itu sama; hanya sekarang jauh lebih sulit untuk membuktikan bahwa mungkinModify tidak pernah mengubah array [1], karena kita sudah memberikannya sebuah pointer; Kucing itu diluar kantung. Sekarang ini harus melakukan bukti yang jauh lebih sulit: analisis statis dari kemungkinanModify untuk membuktikan tidak pernah menulis ke & x + 1. Ini juga harus membuktikan bahwa ia tidak pernah menyimpan pointer yang bisa merujuk ke array [0], yang hanya sebagai rumit.

Penyusun modern semakin lebih baik dan lebih baik pada analisis statis, tetapi selalu menyenangkan untuk membantu mereka dan menggunakan referensi.

Tentu saja, pembatasan optimasi yang cerdas seperti itu, kompiler memang akan mengubah referensi menjadi pointer ketika diperlukan.


50
2017-09-11 20:12



Referensi tidak akan pernah bisa NULL.


32
2018-05-20 19:26