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

3263

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

Tapi apa perbedaannya?

prakash
sumber
100
Saya pikir poin 2 harus "Pointer diperbolehkan menjadi NULL tetapi referensi tidak. Hanya kode yang salah dapat membuat referensi NULL dan perilakunya tidak terdefinisi."
Mark Ransom
19
Pointer hanyalah tipe objek lain, dan seperti objek apa pun di C ++, mereka bisa berupa variabel. Referensi di sisi lain tidak pernah objek, hanya variabel.
Kerrek SB
19
Ini mengkompilasi tanpa peringatan: int &x = *(int*)0;pada gcc. Referensi memang dapat menunjuk ke NULL.
Calmarius
20
referensi adalah alias variabel
Khaled.K
20
Saya suka bagaimana kalimat pertama adalah kekeliruan total. Referensi memiliki semantik mereka sendiri.
Lightness Races in Orbit

Jawaban:

1708
  1. Pointer 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 dapat, dan harus ditetapkan pada inisialisasi:

    int x = 5;
    int y = 6;
    int &r = x;
    
  2. Pointer 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 ruang pada stack. Karena referensi memiliki alamat yang sama dengan variabel asli itu sendiri, aman untuk menganggap referensi sebagai nama lain untuk variabel yang sama. Catatan: Poin penunjuk bisa berada di tumpukan atau tumpukan. Ditto referensi. Klaim saya dalam pernyataan ini bukanlah bahwa pointer harus mengarah ke stack. Pointer hanyalah variabel yang menyimpan alamat memori. Variabel ini ada di tumpukan. Karena referensi memiliki ruang sendiri di stack, dan karena alamatnya sama dengan variabel, referensi. Lebih lanjut tentang tumpukan vs tumpukan. Ini menyiratkan bahwa ada alamat asli dari referensi bahwa kompiler tidak akan memberi tahu Anda.

    int x = 0;
    int &r = x;
    int *p = &x;
    int *p2 = &r;
    assert(p == p2);
    
  3. Anda dapat memiliki petunjuk ke petunjuk ke petunjuk yang menawarkan tingkat tipuan ekstra. 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 nullptrsecara langsung, sedangkan referensi tidak bisa. Jika Anda berusaha cukup keras, dan Anda tahu caranya, Anda dapat membuat alamat referensi nullptr. Demikian juga, jika Anda mencoba cukup keras, Anda dapat memiliki referensi ke sebuah 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 beralih pada array; Anda dapat menggunakan ++untuk pergi ke item berikutnya yang menunjuk ke pointer, dan + 4untuk pergi ke elemen ke-5. Ini tidak masalah ukuran objek apa yang ditunjuk oleh pointer.

  6. Pointer perlu *direferensikan untuk mengakses lokasi memori yang ditunjuknya, sedangkan referensi dapat digunakan secara langsung. Pointer ke kelas / struct digunakan ->untuk mengakses anggota itu sedangkan referensi menggunakan a ..

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

  8. Referensi Const dapat terikat pada temporal. Pointer tidak dapat (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.

Brian R. Bondy
sumber
23
... tetapi dereferencing NULL tidak ditentukan. Misalnya, Anda tidak dapat menguji apakah referensi NULL (mis., & Ref == NULL).
Pat Notz
69
Nomor 2 tidak benar. Referensi bukan hanya "nama lain untuk variabel yang sama." Referensi dapat diteruskan ke fungsi, disimpan di kelas, dll. Dengan cara yang sangat mirip dengan pointer. Mereka ada secara independen dari variabel yang mereka tuju.
Taman Derek
31
Brian, tumpukan itu tidak relevan. Referensi dan petunjuk tidak harus mengambil tempat di stack. Keduanya dapat dialokasikan di heap.
Taman Derek
22
Brian, fakta bahwa variabel (dalam hal ini pointer atau referensi) memerlukan ruang tidak berarti itu membutuhkan ruang pada stack. Pointer dan referensi mungkin tidak hanya menunjuk ke heap, mereka sebenarnya dapat dialokasikan pada heap.
Taman Derek
38
perbedaan penting lainnya: referensi tidak dapat dimasukkan ke dalam array
Johannes Schaub - litb
384

Apa itu referensi C ++ ( untuk programmer C )

Sebuah referensi dapat dianggap sebagai pointer konstan (tidak harus bingung dengan pointer ke nilai konstan!) Dengan tipuan otomatis, yaitu compiler akan menerapkan *operator yang untuk Anda.

Semua referensi harus diinisialisasi dengan nilai bukan nol atau kompilasi akan gagal. Tidak mungkin untuk mendapatkan alamat referensi - operator alamat akan mengembalikan alamat nilai referensi sebagai gantinya - juga tidak mungkin untuk melakukan aritmatika pada referensi.

Pemrogram C mungkin tidak menyukai referensi C ++ karena tidak akan lagi terlihat ketika tipuan terjadi atau jika suatu argumen dilewatkan oleh nilai atau dengan pointer tanpa melihat tanda tangan fungsi.

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

Pertimbangkan pernyataan berikut dari C ++ FAQ :

Meskipun referensi sering diimplementasikan menggunakan alamat dalam bahasa assembly yang mendasari, jangan tidak memikirkan referensi sebagai mencari pointer lucu untuk sebuah objek. Referensi adalah objek. Ini bukan penunjuk ke objek, atau salinan dari objek. Itu adalah objeknya.

Tetapi jika referensi benar - benar objek, bagaimana mungkin ada referensi yang menggantung? Dalam bahasa yang tidak dikelola, tidak mungkin bagi referensi untuk menjadi lebih 'aman' daripada pointer - umumnya tidak ada cara untuk secara andal alias nilai melintasi batas cakupan!

Mengapa saya menganggap referensi C ++ bermanfaat

Berasal dari latar belakang C, referensi C ++ mungkin terlihat seperti konsep yang agak konyol, tetapi orang harus tetap menggunakannya alih-alih dengan petunjuk jika memungkinkan: Ketidaktepatan otomatis menjadi nyaman, dan referensi menjadi sangat berguna ketika berhadapan dengan RAII - tetapi bukan karena adanya persepsi keselamatan keuntungan, tetapi lebih karena mereka membuat penulisan kode idiomatis kurang canggung.

RAII adalah salah satu konsep sentral C ++, tetapi berinteraksi 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 harus lebih mudah daripada alternatif.

Christoph
sumber
17
@ Kriss: Tidak, Anda juga bisa mendapatkan referensi yang menggantung dengan mengembalikan variabel otomatis dengan referensi.
Ben Voigt
12
@ Kriss: Hampir tidak mungkin bagi kompiler untuk mendeteksi dalam kasus umum. Pertimbangkan fungsi anggota yang mengembalikan referensi ke variabel anggota kelas: itu aman dan tidak boleh dilarang oleh kompilator. Kemudian penelepon yang memiliki instance otomatis kelas itu, memanggil fungsi anggota itu, dan mengembalikan referensi. Presto: referensi menggantung. Dan ya, itu akan menyebabkan masalah, @ Kriss: itu maksud saya. Banyak orang mengklaim bahwa kelebihan referensi daripada pointer adalah bahwa referensi selalu valid, tetapi ternyata tidak begitu.
Ben Voigt
4
@ Kriss: Tidak, referensi ke objek durasi penyimpanan otomatis sangat berbeda dari objek sementara. Ngomong-ngomong, saya hanya memberikan contoh tandingan pada pernyataan Anda bahwa Anda hanya bisa mendapatkan referensi yang tidak valid dengan mendereferensikan pointer yang tidak valid. Christoph benar - referensi tidak lebih aman daripada pointer, sebuah program yang menggunakan referensi secara eksklusif masih dapat merusak keamanan jenis.
Ben Voigt
7
Referensi bukan sejenis penunjuk. Mereka adalah nama baru untuk objek yang ada.
catphive
18
@catphive: true jika Anda menggunakan semantik bahasa, tidak benar jika Anda benar-benar melihat implementasinya; C ++ adalah bahasa yang jauh lebih 'ajaib' dari bahasa C, dan jika Anda menghapus sihir dari referensi, Anda akan mendapatkan sebuah pointer
Christoph
191

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

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 dari penggabungan. Sedangkan s3_referensi pada intinya menjadi objek sementara. Ini benar-benar referensi ke objek sementara yang sekarang memiliki umur yang sama dengan referensi.

Jika Anda mencoba ini tanpa constharus gagal untuk dikompilasi. Anda tidak dapat mengikat referensi non-const ke objek sementara, Anda juga tidak dapat mengambil alamatnya untuk hal tersebut.

Matt Price
sumber
5
tapi apa gunanya untuk ini?
Ahmad Mushtaq
20
Nah, s3_copy akan membuat sementara dan kemudian menyalin konstruksinya menjadi s3_copy sedangkan s3_reference langsung menggunakan sementara. Maka untuk menjadi benar-benar hebat Anda perlu melihat Pengoptimalan Nilai Pengembalian di mana kompiler diizinkan untuk menghilangkan konstruksi salinan dalam kasus pertama.
Harga Matt
6
@ DigitalSurgeon: Keajaiban di sana cukup kuat. Umur objek diperpanjang oleh fakta yang const &mengikat, dan hanya ketika referensi keluar dari ruang lingkup destruktor dari tipe referensi aktual (dibandingkan dengan tipe referensi, yang bisa menjadi basis) disebut. Karena ini adalah referensi, tidak ada pemotongan yang terjadi di antara keduanya.
David Rodríguez - dribeas
9
Update untuk C ++ 11: kalimat terakhir harus membaca "Anda tidak bisa mengikat lvalue referensi non-const untuk sementara" karena Anda dapat mengikat non-const nilai p referensi untuk sementara, dan memiliki perilaku hidup-memperpanjang sama.
Oktalist
4
@AhmadMushtaq: Penggunaan utama ini adalah kelas turunan . Jika tidak ada warisan yang terlibat, Anda mungkin juga menggunakan semantik nilai, yang akan murah atau gratis karena RVO / memindahkan konstruksi. Tetapi jika sudah Animal x = fast ? getHare() : getTortoise()maka xakan menghadapi masalah mengiris klasik, sementara Animal& x = ...akan bekerja dengan benar.
Arthur Tacca
128

Terlepas dari gula sintaksis, referensi adalah constpenunjuk ( bukan penunjuk ke a const). Anda harus menetapkan apa yang dirujuk ketika Anda mendeklarasikan variabel referensi, dan Anda tidak dapat mengubahnya nanti.

Perbarui: sekarang saya memikirkannya lagi, ada perbedaan penting.

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

Sasaran referensi tidak dapat diganti dengan cara apa pun selain dari UB.

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


sumber
8
Saya pikir ini adalah jawaban terbaik sejauh ini. Yang lain berbicara tentang referensi dan petunjuk seperti mereka adalah binatang buas yang berbeda dan kemudian menguraikan bagaimana mereka berbeda dalam perilaku. Itu tidak membuat semuanya jadi lebih mudah. Saya selalu memahami referensi sebagai T* constdengan gula sintaksis yang berbeda (yang terjadi untuk menghilangkan banyak * dan & dari kode Anda).
Carlo Wood
2
"Target const pointer dapat diganti dengan mengambil alamatnya dan menggunakan cast const." Melakukannya adalah perilaku yang tidak terdefinisi. Lihat stackoverflow.com/questions/25209838/… untuk detailnya.
dgnuff
1
Mencoba mengubah rujukan referensi atau nilai pointer const (atau skalar sembarang) adalah kesetaraan ilegal. Apa yang dapat Anda lakukan: menghapus kualifikasi konst yang ditambahkan oleh konversi implisit: int i; int const *pci = &i; /* implicit conv to const int* */ int *pi = const_cast<int*>(pci);OK.
curiousguy
1
Perbedaannya di sini adalah UB versus secara harfiah tidak mungkin. Tidak ada sintaks di C ++ yang akan membiarkan Anda mengubah titik referensi di.
Bukan tidak mungkin, lebih sulit, Anda hanya dapat mengakses area memori dari pointer yang memodelkan referensi itu dan mengubah kontennya. Itu tentu bisa dilakukan.
Nicolas Bousquet
126

Berlawanan dengan pendapat umum, dimungkinkan 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 berusaha menemukannya. Referensi secara inheren tidak aman di C ++!

Secara teknis ini adalah referensi yang tidak valid , bukan referensi nol. C ++ tidak mendukung referensi nol sebagai konsep yang mungkin Anda temukan dalam bahasa lain. Ada beberapa jenis referensi yang tidak valid juga. Setiap referensi yang tidak valid menimbulkan momok perilaku tidak terdefinisi , seperti halnya menggunakan pointer yang tidak valid.

Kesalahan aktual adalah dalam referensi dari pointer NULL, sebelum penugasan ke referensi. Tapi saya tidak mengetahui adanya kompiler yang akan menghasilkan kesalahan pada kondisi itu - kesalahan menyebar ke titik lebih jauh dalam kode. Itulah yang membuat masalah ini begitu berbahaya. Sebagian besar waktu, jika Anda mensetere pointer NULL, Anda crash tepat di tempat itu dan tidak perlu banyak debugging untuk mengetahuinya.

Contoh saya di atas pendek dan dibuat-buat. Ini contoh dunia 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 salah bentuk, dan sekali Anda memilikinya Anda mendapatkan perilaku yang tidak terdefinisi. Ini tidak pernah masuk akal untuk memeriksa referensi null; misalnya Anda dapat mencoba if(&bar==NULL)...tetapi kompilator mungkin mengoptimalkan pernyataan tidak ada! Referensi yang valid tidak akan pernah NULL sehingga dari pandangan kompiler perbandingan selalu salah, dan bebas untuk menghilangkan ifklausa sebagai kode mati - ini adalah inti dari perilaku yang tidak terdefinisi.

Cara yang tepat untuk menghindari masalah adalah dengan menghindari penereferensian pointer NULL untuk membuat referensi. Inilah cara otomatis untuk melakukannya.

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 dari seseorang dengan keterampilan menulis yang lebih baik, lihat Referensi Null dari Jim Hyslop dan Herb Sutter.

Untuk contoh lain tentang bahaya penereferensian penunjuk nol, lihat Mengekspos perilaku tidak terdefinisi saat mencoba mengirim kode ke platform lain oleh Raymond Chen.

Mark tebusan
sumber
63
Kode tersebut mengandung perilaku yang tidak terdefinisi. Secara teknis, Anda tidak dapat melakukan apa pun dengan pointer nol kecuali mengaturnya, dan membandingkannya. Setelah program Anda memanggil perilaku yang tidak terdefinisi, ia dapat melakukan apa saja, termasuk tampil bekerja dengan benar sampai Anda memberikan demo kepada bos besar.
KeithB
9
mark memiliki argumen yang valid. argumen bahwa sebuah pointer bisa NULL dan karenanya Anda harus mengeceknya juga tidak nyata: jika Anda mengatakan suatu fungsi memerlukan non-NULL, maka pemanggil harus melakukan itu. jadi jika penelepon tidak dia memohon perilaku yang tidak jelas. seperti yang dilakukan mark dengan referensi yang buruk
Johannes Schaub - litb
13
Uraiannya salah. Kode ini mungkin atau mungkin tidak membuat referensi yang NULL. Perilakunya tidak terdefinisi. Mungkin membuat referensi yang benar-benar valid. Mungkin gagal membuat referensi sama sekali.
David Schwartz
7
@ David Schwartz, jika saya berbicara tentang cara segala sesuatu harus bekerja sesuai dengan standar, Anda pasti benar. Tapi bukan itu yang saya bicarakan - saya sedang berbicara tentang perilaku yang diamati aktual dengan kompiler yang sangat populer, dan ekstrapolasi berdasarkan pengetahuan saya tentang kompiler dan arsitektur CPU khas untuk apa yang mungkin akan terjadi. Jika Anda yakin referensi lebih unggul daripada pointer karena mereka lebih aman dan tidak menganggap bahwa referensi bisa buruk, Anda akan bingung oleh masalah sederhana suatu hari seperti saya.
Mark Ransom
6
Mendereferensi null pointer salah. Program apa pun yang melakukan itu, bahkan untuk menginisialisasi referensi adalah salah. Jika Anda menginisialisasi referensi dari sebuah pointer, Anda harus selalu memeriksa apakah pointer itu valid. Bahkan jika ini berhasil, objek yang mendasarinya dapat dihapus kapan saja meninggalkan referensi untuk merujuk ke objek yang tidak ada, kan? Apa yang Anda katakan adalah hal yang baik. Saya pikir masalah sebenarnya di sini adalah bahwa referensi TIDAK perlu diperiksa untuk "nullness" ketika Anda melihat satu dan pointer harus, minimal, ditegaskan.
t0rakka
115

Anda lupa bagian terpenting:

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

foo.baradalah jelas lebih unggul foo->bardalam cara yang sama bahwa vi adalah jelas lebih unggul Emacs :-)

Orion Edwards
sumber
4
@Orion Edwards> akses anggota dengan penggunaan pointer ->> akses anggota dengan penggunaan referensi. Ini tidak 100% benar. Anda dapat memiliki referensi ke sebuah pointer. Dalam hal ini Anda akan mengakses anggota dari penunjuk yang dirujuk menggunakan -> struct Node {Node * next; }; Simpul * pertama; // p adalah referensi ke pointer void foo (Node * & p) {p-> next = pertama; } Node * bar = Node baru; foo (bar); - OP: Apakah Anda terbiasa dengan konsep nilai dan nilai?
3
Pointer pintar memiliki keduanya. (metode pada kelas pointer pintar) dan -> (metode pada tipe yang mendasarinya).
JBRWilkinson
1
@ user6105 Pernyataan Orion Edwards sebenarnya 100% benar. "akses anggota dari penunjuk yang direferensikan" Suatu penunjuk tidak memiliki anggota. Objek yang ->dirujuk oleh pointer memiliki anggota, dan akses ke mereka itulah yang menyediakan referensi ke pointer, seperti halnya pointer itu sendiri.
Max Truxa
1
kenapa begitu .dan ->ada hubungannya dengan vi dan emacs :)
artm
10
@artM - itu lelucon, dan mungkin tidak masuk akal bagi penutur bahasa Inggris non-pribumi. Permintaan maaf saya. Untuk menjelaskan, apakah vi lebih baik daripada emacs sepenuhnya subjektif. Beberapa orang berpikir vi jauh lebih unggul, dan yang lain berpikir sebaliknya. Demikian pula, saya pikir menggunakan .lebih baik daripada menggunakan ->, tetapi sama seperti vi vs emacs, itu sepenuhnya subjektif dan Anda tidak dapat membuktikan apa pun
Orion Edwards
74

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

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

Kompiler pengoptimal mungkin menyadari bahwa kita sedang mengakses cukup banyak [0] dan [1]. Alangkah baiknya untuk 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 optimasi seperti itu, perlu membuktikan bahwa tidak ada yang dapat mengubah array [1] selama panggilan. Ini agak mudah dilakukan. i tidak pernah kurang dari 2, jadi array [i] tidak pernah bisa merujuk ke array [1]. maybeModify () diberikan a0 sebagai referensi (aliasing array [0]). Karena tidak ada aritmatika "referensi", kompiler hanya perlu membuktikan bahwa mungkinModify tidak pernah mendapatkan alamat x, dan telah membuktikan bahwa tidak ada yang mengubah array [1].

Itu juga harus membuktikan bahwa tidak ada cara panggilan masa depan dapat membaca / menulis [0] sementara kami memiliki salinan register sementara di a0. Ini sering sepele untuk dibuktikan, karena dalam banyak kasus jelas bahwa referensi tidak pernah disimpan dalam struktur permanen seperti instance 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];
    }
}

Tingkah lakunya sama; hanya sekarang ini jauh lebih sulit untuk membuktikan bahwa mungkinModify tidak pernah memodifikasi array [1], karena kami telah memberikannya sebuah pointer; Kucing itu diluar kantung. Sekarang ia harus melakukan bukti yang jauh lebih sulit: analisis statis mungkinModify untuk membuktikannya tidak pernah menulis ke & x + 1. Ia juga harus membuktikan bahwa itu tidak pernah menyelamatkan pointer yang dapat merujuk ke array [0], yang hanya rumit.

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

Tentu saja, kecuali optimasi cerdas, kompiler memang akan mengubah referensi menjadi petunjuk ketika dibutuhkan.

EDIT: Lima tahun setelah memposting jawaban ini, saya menemukan perbedaan teknis yang sebenarnya di mana referensi berbeda dari hanya cara berbeda dalam memandang konsep pengalamatan yang sama. Referensi dapat memodifikasi umur objek sementara dengan cara yang pointer tidak bisa.

F createF(int argument);

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

Biasanya objek sementara seperti yang dibuat oleh panggilan untuk createF(5)dihancurkan pada akhir ekspresi. Namun, dengan mengikat objek itu ke referensi,, refC ++ akan memperpanjang umur objek sementara itu sampai refkeluar dari ruang lingkup.

Cort Ammon
sumber
Benar, tubuh memang harus terlihat. Namun, menentukan bahwa maybeModifytidak mengambil alamat yang terkait dengan hal xini jauh lebih mudah daripada membuktikan bahwa sekelompok aritmatika pointer tidak terjadi.
Cort Ammon
Saya percaya pengoptimal sudah melakukan itu "sekelompok aritemetik pointer tidak terjadi" memeriksa banyak alasan lain.
Ben Voigt
"Referensi sangat mirip dengan pointer" - secara semantik, dalam konteks yang sesuai - tetapi dalam hal kode yang dihasilkan, hanya dalam beberapa implementasi dan tidak melalui definisi / persyaratan. Saya tahu Anda telah menunjukkan hal ini, dan saya tidak setuju dengan salah satu pos Anda secara praktis, tetapi kami sudah memiliki terlalu banyak masalah dengan orang-orang membaca terlalu banyak ke dalam deskripsi steno seperti 'referensi seperti / biasanya diterapkan sebagai petunjuk' .
underscore_d
Saya memiliki perasaan bahwa seseorang salah menandai komentar usang di sepanjang baris void maybeModify(int& x) { 1[&x]++; }, yang dibahas oleh komentar lain di atas
Ben Voigt
69

Sebenarnya, referensi tidak benar-benar seperti pointer.

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

Saat Anda membuat referensi, Anda hanya memberi tahu kompiler bahwa Anda menetapkan nama lain ke variabel pointer; itu sebabnya referensi tidak dapat "menunjuk ke nol", karena variabel tidak bisa, dan tidak bisa.

Pointer adalah variabel; mereka berisi alamat beberapa variabel lain, atau bisa nol. Yang penting adalah bahwa pointer memiliki nilai, sedangkan referensi hanya memiliki variabel yang direferensikan.

Sekarang beberapa penjelasan tentang kode asli:

int a = 0;
int& b = a;

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

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

int a;
increment(a);

Saat memanggil fungsi, kompiler biasanya menghasilkan ruang memori untuk argumen yang akan disalin. Fungsi tanda tangan mendefinisikan ruang yang harus dibuat dan memberikan nama yang harus digunakan untuk ruang ini. Mendeklarasikan parameter sebagai referensi hanya memberi tahu kompiler untuk menggunakan ruang memori variabel input alih-alih mengalokasikan ruang memori baru selama pemanggilan metode. Mungkin aneh untuk mengatakan bahwa fungsi Anda akan secara langsung memanipulasi variabel yang dideklarasikan dalam lingkup panggilan, tetapi ingat bahwa ketika menjalankan kode yang dikompilasi, tidak ada lagi ruang lingkup; hanya ada memori datar, dan kode fungsi Anda dapat memanipulasi variabel apa pun.

Sekarang mungkin ada beberapa kasus di mana kompiler Anda mungkin tidak dapat mengetahui referensi saat kompilasi, seperti ketika menggunakan variabel extern. Jadi referensi mungkin atau mungkin tidak diterapkan sebagai penunjuk dalam kode yang mendasarinya. Tetapi dalam contoh yang saya berikan kepada Anda, kemungkinan besar tidak akan diimplementasikan dengan pointer.

Vincent Robert
sumber
2
Referensi adalah referensi ke nilai-l, tidak harus ke variabel. Karena itu, itu jauh lebih dekat ke pointer daripada alias nyata (sebuah kompilasi waktu). Contoh ekspresi yang dapat direferensikan adalah * p atau bahkan * p ++
5
Benar, saya hanya menunjukkan fakta bahwa referensi mungkin tidak selalu mendorong variabel baru di stack seperti yang akan dilakukan oleh pointer baru.
Vincent Robert
1
@VincentRobert: Ini akan bertindak sama dengan sebuah pointer ... jika fungsi ini digarisbawahi, baik referensi dan pointer akan dioptimalkan. Jika ada panggilan fungsi, alamat objek harus diteruskan ke fungsi.
Ben Voigt
1
int * p = NULL; int & r = * p; referensi menunjuk ke NULL; if (r) {} -> boOm;)
sree
2
Fokus pada tahap kompilasi ini tampak bagus, sampai Anda ingat bahwa referensi dapat diedarkan pada saat runtime, di mana titik alias statis keluar dari jendela. (Dan kemudian, referensi biasanya diimplementasikan sebagai pointer, tetapi standar tidak memerlukan metode ini.)
underscore_d
45

Referensi tidak pernah bisa NULL.

Cole Johnson
sumber
10
Lihat jawaban Mark Ransom untuk contoh tandingan. Ini adalah mitos yang paling sering ditegaskan tentang referensi, tetapi itu adalah mitos. Satu-satunya jaminan yang Anda miliki menurut standar adalah, bahwa Anda segera memiliki UB ketika Anda memiliki referensi NULL. Tapi itu sama dengan mengatakan "Mobil ini aman, tidak akan pernah bisa keluar dari jalan. (Kami tidak bertanggung jawab atas apa yang mungkin terjadi jika Anda mengarahkannya dari jalan. Mungkin saja akan meledak.)"
cmaster - mengembalikan monica
17
@ cmaster: Dalam program yang valid , referensi tidak boleh nol. Tapi sebuah pointer bisa. Ini bukan mitos, ini fakta.
user541686
8
@Mehrdad Ya, program yang valid tetap ada di jalan. Tetapi tidak ada penghalang lalu lintas untuk memastikan bahwa program Anda benar-benar melakukannya. Sebagian besar jalan sebenarnya tidak memiliki tanda. Jadi sangat mudah untuk keluar dari jalan di malam hari. Dan sangat penting untuk men-debug bug seperti yang Anda tahu ini bisa terjadi: referensi nol dapat menyebar sebelum crash program Anda, seperti halnya pointer nol bisa. Dan ketika itu Anda memiliki kode seperti void Foo::bar() { virtual_baz(); }segfaults. Jika Anda tidak mengetahui bahwa referensi mungkin nol, Anda tidak dapat melacak nol kembali ke asalnya.
cmaster - mengembalikan monica
4
int * p = NULL; int & r = * p; referensi menunjuk ke NULL; if (r) {} -> boOm;) -
sree
10
@sree int &r=*p;adalah perilaku yang tidak terdefinisi. Pada saat itu, Anda tidak memiliki "referensi yang menunjuk ke NULL," Anda memiliki program yang tidak lagi dapat dipertimbangkan sama sekali .
cdhowie
35

Sementara kedua referensi dan pointer digunakan untuk mengakses nilai lain secara tidak langsung, ada dua perbedaan penting antara referensi dan pointer. Yang pertama adalah bahwa referensi selalu merujuk ke objek: Ini adalah kesalahan untuk menentukan referensi tanpa menginisialisasi itu. Perilaku penugasan adalah perbedaan penting kedua: Menugaskan referensi mengubah objek yang menjadi acuan referensi; itu tidak mengulang referensi ke objek lain. Setelah diinisialisasi, referensi selalu merujuk ke objek dasar yang sama.

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

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

Setelah penugasan, ival, objek yang ditangani oleh pi tetap tidak berubah. Tugas mengubah nilai pi, menjadikannya menunjuk ke objek yang berbeda. Sekarang pertimbangkan program serupa yang memberikan dua referensi:

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

Tugas ini mengubah ival, nilai yang dirujuk oleh ri, dan bukan referensi itu sendiri. Setelah penugasan, kedua referensi masih merujuk ke objek aslinya, dan nilai dari objek tersebut sekarang sama juga.

Kunal Vyas
sumber
"referensi selalu merujuk ke objek" benar-benar salah
Ben Voigt
32

Ada perbedaan semantik yang mungkin tampak esoteris jika Anda tidak terbiasa mempelajari bahasa komputer secara abstrak atau bahkan mode akademik.

Pada level tertinggi, gagasan referensi adalah bahwa mereka transparan "alias". Komputer Anda mungkin menggunakan alamat untuk membuatnya berfungsi, tetapi Anda tidak perlu khawatir tentang itu: Anda seharusnya menganggapnya sebagai "hanya nama lain" untuk objek yang ada dan sintaksinya mencerminkan hal itu. Mereka lebih ketat daripada pointer sehingga kompiler Anda dapat lebih andal memperingatkan Anda ketika Anda akan membuat referensi menggantung, daripada ketika Anda akan membuat pointer menggantung.

Selain itu, tentu saja ada beberapa perbedaan praktis antara petunjuk dan referensi. Sintaks untuk menggunakannya jelas berbeda, dan Anda tidak bisa "duduk kembali" referensi, memiliki referensi ke ketiadaan, atau memiliki pointer ke referensi.

Lightness Races di Orbit
sumber
27

Referensi adalah alias untuk variabel lain sedangkan pointer menyimpan alamat memori variabel. Referensi umumnya digunakan sebagai parameter fungsi sehingga objek yang dikirimkan bukan salinan tetapi objek itu sendiri.

    void fun(int &a, int &b); // A common usage of references.
    int a = 0;
    int &b = a; // b is an alias for a. Not so common to use. 
fatma.ekici
sumber
20

Tidak masalah berapa banyak ruang yang dibutuhkan karena Anda tidak dapat benar-benar melihat efek samping (tanpa mengeksekusi kode) dari ruang apa pun yang diperlukan.

Di sisi lain, satu perbedaan utama antara referensi dan pointer adalah bahwa temporaries ditugaskan untuk referensi const hidup sampai referensi const keluar dari ruang lingkup.

Sebagai contoh:

class scope_test
{
public:
    ~scope_test() { printf("scope_test done!\n"); }
};

...

{
    const scope_test &test= scope_test();
    printf("in scope\n");
}

akan dicetak:

in scope
scope_test done!

Ini adalah mekanisme bahasa yang memungkinkan ScopeGuard berfungsi.

MSN
sumber
1
Anda tidak dapat mengambil alamat referensi, tetapi itu tidak berarti bahwa mereka tidak mengambil ruang secara fisik. Kecuali optimisasi, mereka pasti bisa.
Lightness Races dalam Orbit
2
Meskipun berdampak, "Referensi pada stack tidak memakan ruang sama sekali" adalah benar-benar salah.
Lightness Races di Orbit
1
@ Tomalak, yah, itu tergantung juga pada kompiler. Tapi ya, mengatakan itu agak membingungkan. Saya kira itu akan kurang membingungkan untuk hanya menghapus itu.
MSN
1
Dalam setiap kasus khusus tertentu mungkin atau mungkin tidak. Jadi "tidak" sebagai pernyataan kategoris adalah salah. Itu yang saya katakan. :) [Saya tidak ingat apa yang dikatakan standar tentang masalah ini; aturan anggota referensi dapat memberikan aturan umum "referensi dapat mengambil ruang", tetapi saya tidak memiliki salinan standar saya di sini di pantai: D]
Lightness Races in Orbit
20

Ini didasarkan pada tutorial . Apa yang tertulis membuatnya lebih jelas:

>>> The address that locates a variable within memory is
    what we call a reference to that variable. (5th paragraph at page 63)

>>> The variable that stores the reference to another
    variable is what we call a pointer. (3rd paragraph at page 64)

Hanya untuk mengingat itu,

>>> reference stands for memory location
>>> pointer is a reference container (Maybe because we will use it for
several times, it is better to remember that reference.)

Terlebih lagi, karena kita bisa merujuk ke hampir semua tutorial pointer, pointer adalah objek yang didukung oleh aritmatika pointer yang membuat pointer mirip dengan array.

Lihatlah pernyataan berikut,

int Tom(0);
int & alias_Tom = Tom;

alias_Tomdapat dipahami sebagai alias of a variable(berbeda dengan typedef, yaitu alias of a type) Tom. Tidak apa-apa untuk melupakan terminologi pernyataan tersebut untuk membuat referensi Tom.

Kehidupan
sumber
1
Dan jika kelas memiliki variabel referensi, itu harus diinisialisasi dengan nullptr atau objek yang valid dalam daftar inisialisasi.
Misgevolution
1
Kata-kata dalam jawaban ini terlalu membingungkan untuk digunakan secara nyata. Juga, @Misgevolution, apakah Anda dengan serius merekomendasikan kepada pembaca untuk menginisialisasi referensi dengan nullptr? Apakah Anda benar-benar membaca bagian lain dari utas ini, atau ...?
underscore_d
1
Sayang saya, maaf untuk hal bodoh yang saya katakan. Saya pasti kurang tidur pada saat itu. 'menginisialisasi dengan nullptr' sama sekali salah.
Misgevolution
19

Referensi bukan nama lain yang diberikan untuk sebagian memori. Ini adalah pointer yang tidak dapat diubah yang secara otomatis di-de-referensikan pada penggunaan. Pada dasarnya bermuara pada:

int& j = i;

Secara internal menjadi

int* const j = &i;
penyamak alam
sumber
13
Ini bukan apa yang dikatakan Standar C ++, dan tidak diperlukan bagi kompiler untuk mengimplementasikan referensi dengan cara yang dijelaskan oleh jawaban Anda.
jogojapan
@jogojapan: Cara apa pun yang valid untuk kompiler C ++ untuk mengimplementasikan referensi juga merupakan cara yang valid untuk mengimplementasikan constpointer. Fleksibilitas itu tidak membuktikan bahwa ada perbedaan antara referensi dan pointer.
Ben Voigt
2
@ BenVoigt Mungkin benar bahwa setiap implementasi yang valid dari satu juga merupakan implementasi yang valid dari yang lain, tetapi itu tidak mengikuti dengan cara yang jelas dari definisi kedua konsep ini. Jawaban yang baik akan dimulai dari definisi, dan menunjukkan mengapa klaim tentang keduanya pada akhirnya sama adalah benar. Jawaban ini tampaknya semacam komentar pada beberapa jawaban lain.
jogojapan
Referensi adalah nama lain yang diberikan kepada suatu objek. Kompiler diizinkan untuk memiliki segala jenis implementasi, selama Anda tidak dapat membedakannya, ini dikenal sebagai aturan "as-jika". Bagian penting di sini adalah Anda tidak dapat membedakannya. Jika Anda dapat menemukan bahwa pointer tidak memiliki penyimpanan, kompiler salah. Jika Anda dapat menemukan bahwa referensi tidak memiliki penyimpanan, kompiler masih sesuai.
sp2danny
18

Jawaban langsungnya

Apa referensi di C ++? Beberapa contoh spesifik dari tipe yang bukan tipe objek .

Apa itu pointer di C ++? Beberapa contoh spesifik dari tipe yang merupakan tipe objek .

Dari definisi tipe objek ISO C ++ :

Sebuah objek jenis adalah (mungkin cv -qualified) jenis yang bukan tipe fungsi, bukan tipe referensi, dan tidak cv batal.

Mungkin penting untuk mengetahui, tipe objek adalah kategori level-atas dari semesta tipe dalam C ++. Referensi juga merupakan kategori tingkat atas. Tapi pointer tidak.

Pointer dan referensi disebutkan bersama dalam konteks tipe majemuk . Ini pada dasarnya karena sifat sintaks deklarator yang diwarisi dari (dan diperluas) C, yang tidak memiliki referensi. (Selain itu, ada lebih dari satu jenis deklarator referensi sejak C ++ 11, sementara pointer masih "tidak tergabung": &+ &&vs *..) Jadi penyusunan bahasa yang spesifik dengan "ekstensi" dengan gaya C yang serupa dalam konteks ini agak masuk akal . (Saya masih akan berpendapat bahwa sintaks declarators limbah yang ekspresif sintaksis banyak , membuat kedua pengguna manusia dan implementasi frustasi. Dengan demikian, semua dari mereka tidak memenuhi syarat untuk built-indalam desain bahasa baru. Ini adalah topik yang sama sekali berbeda tentang desain PL.)

Kalau tidak, itu tidak signifikan bahwa pointer dapat dikualifikasikan sebagai jenis jenis tertentu dengan referensi bersama. Mereka hanya berbagi terlalu sedikit properti umum selain kesamaan sintaksis, sehingga tidak perlu menyatukannya dalam banyak kasus.

Perhatikan pernyataan di atas hanya menyebutkan "pointer" dan "referensi" sebagai tipe. Ada beberapa pertanyaan yang tertarik tentang instance mereka (seperti variabel). Ada juga banyak kesalahpahaman.

Perbedaan kategori tingkat atas sudah dapat mengungkapkan banyak perbedaan nyata yang tidak terkait langsung dengan petunjuk:

  • Jenis objek dapat memiliki cvkualifikasi tingkat atas . Referensi tidak bisa.
  • Variabel tipe objek menempati penyimpanan sesuai dengan semantik mesin abstrak . Referensi tidak perlu menempati penyimpanan (lihat bagian tentang kesalahpahaman di bawah ini untuk perincian).
  • ...

Beberapa aturan khusus tentang referensi:

  • Deklarator majemuk lebih membatasi referensi.
  • Referensi dapat runtuh .
    • Aturan khusus pada &&parameter (sebagai "referensi penerusan") berdasarkan pada runtuhan referensi selama pengurangan parameter templat memungkinkan "penerusan yang sempurna" dari parameter.
  • Referensi memiliki aturan khusus dalam inisialisasi. Masa pakai variabel yang dideklarasikan sebagai tipe referensi dapat berbeda dengan objek biasa melalui ekstensi.
    • BTW, beberapa konteks lain seperti inisialisasi yang melibatkan std::initializer_listmengikuti beberapa aturan serupa perpanjangan seumur hidup referensi. Ini adalah kaleng cacing.
  • ...

Kesalahpahaman

Gula sintaksis

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

Secara teknis, ini salah. Referensi bukan gula sintaksis dari fitur lain di C ++, karena mereka tidak dapat secara tepat digantikan oleh fitur lain tanpa perbedaan semantik.

(Demikian pula, lambda ekspresi s yang tidak sintaksis gula dari setiap fitur lain dalam C ++ karena tidak dapat tepat disimulasikan dengan "tidak ditentukan" properti seperti urutan deklarasi variabel ditangkap , yang mungkin penting karena urutan inisialisasi variabel tersebut dapat penting.)

C ++ hanya memiliki beberapa jenis gula sintaksis dalam arti yang ketat ini. Salah satu contoh adalah (diwarisi dari C) operator built-in (non-overloaded) [], yang didefinisikan secara tepat memiliki sifat semantik yang sama dari bentuk kombinasi tertentu dibandingkan operator built-in unary *dan binary+ .

Penyimpanan

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

Pernyataan di atas benar-benar salah. Untuk menghindari kesalahpahaman seperti itu, lihat aturan ISO C ++ sebagai gantinya:

Dari [intro.object] / 1 :

... Objek menempati wilayah penyimpanan dalam periode konstruksinya, sepanjang masa pakainya, dan dalam periode kehancurannya. ...

Dari [dcl.ref] / 4 :

Tidak ditentukan apakah referensi membutuhkan penyimpanan atau tidak.

Perhatikan ini adalah properti semantik .

Pragmatis

Meskipun pointer tidak cukup memenuhi syarat untuk disatukan dengan referensi dalam arti desain bahasa, masih ada beberapa argumen yang membuatnya diperdebatkan untuk membuat pilihan di antara mereka dalam beberapa konteks lain, misalnya, ketika membuat pilihan pada tipe parameter.

Tapi ini bukan keseluruhan cerita. Maksud saya, ada lebih banyak hal daripada petunjuk vs referensi yang harus Anda pertimbangkan.

Jika Anda tidak harus tetap pada pilihan yang terlalu spesifik, dalam kebanyakan kasus jawabannya pendek: Anda tidak memiliki keharusan untuk menggunakan pointer, jadi Anda tidak perlu . Pointer biasanya cukup buruk karena mereka menyiratkan terlalu banyak hal yang tidak Anda harapkan dan mereka akan bergantung pada terlalu banyak asumsi implisit yang merusak pemeliharaan dan (bahkan) portabilitas kode. Mengandalkan pointer secara tidak perlu jelas merupakan gaya yang buruk dan harus dihindari dalam pengertian C ++ modern. Pertimbangkan kembali tujuan Anda dan akhirnya Anda akan menemukan bahwa pointer adalah fitur yang paling terakhir dalam banyak kasus.

  • Terkadang aturan bahasa secara eksplisit membutuhkan jenis khusus untuk digunakan. Jika Anda ingin menggunakan fitur ini, patuhi aturannya.
    • Copy konstruktor membutuhkan jenis tertentu dari cv - &tipe referensi sebagai jenis parameter 1. (Dan biasanya itu harus constmemenuhi syarat.)
    • Langkah konstruktor membutuhkan jenis tertentu dari cv - &&tipe referensi sebagai jenis parameter 1. (Dan biasanya seharusnya tidak ada kualifikasi.)
    • Kelebihan spesifik operator memerlukan jenis referensi atau non referensi. Sebagai contoh:
      • Kelebihan beban operator=sebagai fungsi anggota khusus membutuhkan tipe referensi yang mirip dengan parameter pertama copy / move constructor.
      • Postfix ++membutuhkan dummy int.
      • ...
  • Jika Anda tahu nilai pass-by-value (yaitu menggunakan tipe non-referensi) sudah cukup, gunakan secara langsung, terutama ketika menggunakan implementasi yang mendukung C ++ 17 elisi salinan yang diamanatkan. ( Peringatan : Namun, untuk alasan lengkap tentang perlunya bisa sangat rumit .)
  • Jika Anda ingin mengoperasikan beberapa pegangan dengan kepemilikan, gunakan pointer pintar seperti unique_ptrdan shared_ptr(atau bahkan dengan handebrew sendiri jika Anda mengharuskannya buram ), daripada pointer mentah.
  • Jika Anda melakukan beberapa iterasi pada rentang, gunakan iterator (atau beberapa rentang yang belum disediakan oleh pustaka standar), daripada pointer mentah kecuali Anda yakin pointer mentah akan melakukan lebih baik (misalnya untuk mengurangi ketergantungan header) dalam sangat spesifik kasus.
  • Jika Anda tahu pass-by-value sudah cukup dan Anda ingin beberapa semantik eksplisit dapat dibatalkan, gunakan pembungkus seperti std::optional, daripada pointer mentah.
  • Jika Anda tahu pass-by-value tidak ideal untuk alasan di atas, dan Anda tidak ingin semantik yang dapat dibatalkan, gunakan {lvalue, rvalue, penerusan} -referensi.
  • Bahkan ketika Anda menginginkan semantik seperti pointer tradisional, seringkali ada sesuatu yang lebih tepat, seperti observer_ptrdi Library Fundamental TS.

Satu-satunya pengecualian tidak dapat dikerjakan dalam bahasa saat ini:

  • Ketika Anda menerapkan pointer pintar di atas, Anda mungkin harus berurusan dengan pointer mentah.
  • Rutinitas bahasa-interoperasi spesifik membutuhkan pointer, seperti operator new. (Namun, cv - void*masih sangat berbeda dan lebih aman dibandingkan dengan pointer objek biasa karena mengesampingkan aritmetik pointer yang tidak terduga kecuali Anda mengandalkan ekstensi yang tidak sesuai pada void*GNU.)
  • Pointer fungsi dapat dikonversi dari ekspresi lambda tanpa menangkap, sedangkan referensi fungsi tidak bisa. Anda harus menggunakan pointer fungsi dalam kode non-generik untuk kasus seperti itu, bahkan Anda sengaja tidak ingin nilai nullable.

Jadi, dalam praktiknya, jawabannya sangat jelas: ketika ragu, hindari petunjuk . Anda harus menggunakan pointer hanya ketika ada alasan yang sangat eksplisit bahwa tidak ada hal lain yang lebih tepat. Kecuali beberapa kasus luar biasa yang disebutkan di atas, pilihan seperti itu hampir selalu tidak murni C ++ - spesifik (tetapi cenderung spesifik implementasi bahasa). Contoh seperti itu dapat:

  • Anda harus melayani API gaya lama (C).
  • Anda harus memenuhi persyaratan ABI untuk implementasi C ++ spesifik.
  • Anda harus beroperasi pada saat runtime dengan implementasi bahasa yang berbeda (termasuk berbagai majelis, runtime bahasa dan FFI dari beberapa bahasa klien tingkat tinggi) berdasarkan pada asumsi implementasi tertentu.
  • Anda harus meningkatkan efisiensi terjemahan (kompilasi & penautan) dalam beberapa kasus ekstrem.
  • Anda harus menghindari simbol mengasapi dalam beberapa kasus ekstrim.

Peringatan netralitas bahasa

Jika Anda datang untuk melihat pertanyaan melalui beberapa hasil pencarian Google (tidak spesifik untuk C ++) , ini kemungkinan besar merupakan tempat yang salah.

Referensi dalam C ++ cukup "aneh", karena pada dasarnya tidak kelas: mereka akan diperlakukan sebagai objek atau fungsi yang dimaksud sehingga mereka tidak memiliki kesempatan untuk mendukung beberapa operasi kelas seperti berada operan kiri yang operator akses anggota secara independen ke jenis objek yang dimaksud. Bahasa lain mungkin atau mungkin tidak memiliki batasan serupa pada referensi mereka.

Referensi dalam C ++ kemungkinan tidak akan mempertahankan makna di berbagai bahasa. Misalnya, referensi secara umum tidak menyiratkan properti nonnull pada nilai-nilai seperti mereka di C ++, jadi asumsi seperti itu mungkin tidak berfungsi dalam beberapa bahasa lain (dan Anda akan menemukan contoh tandingan cukup mudah, misalnya Java, C #, ...).

Masih ada beberapa sifat umum di antara referensi dalam berbagai bahasa pemrograman pada umumnya, tetapi mari kita tinggalkan untuk beberapa pertanyaan lain di SO.

(Catatan tambahan: pertanyaannya mungkin lebih penting daripada bahasa "C-like" apa pun yang terlibat, seperti ALGOL 68 vs. PL / I. )

FrankHB
sumber
17

Referensi ke sebuah pointer dimungkinkan dalam C ++, tetapi sebaliknya tidak mungkin berarti pointer ke sebuah referensi tidak mungkin. Referensi ke sebuah pointer menyediakan sintaks yang lebih bersih untuk memodifikasi pointer. Lihat contoh ini:

#include<iostream>
using namespace std;

void swap(char * &str1, char * &str2)
{
  char *temp = str1;
  str1 = str2;
  str2 = temp;
}

int main()
{
  char *str1 = "Hi";
  char *str2 = "Hello";
  swap(str1, str2);
  cout<<"str1 is "<<str1<<endl;
  cout<<"str2 is "<<str2<<endl;
  return 0;
}

Dan perhatikan versi C dari program di atas. Dalam C Anda harus menggunakan pointer ke pointer (beberapa tipuan), dan itu menyebabkan kebingungan dan program mungkin terlihat rumit.

#include<stdio.h>
/* Swaps strings by swapping pointers */
void swap1(char **str1_ptr, char **str2_ptr)
{
  char *temp = *str1_ptr;
  *str1_ptr = *str2_ptr;
  *str2_ptr = temp;
}

int main()
{
  char *str1 = "Hi";
  char *str2 = "Hello";
  swap1(&str1, &str2);
  printf("str1 is %s, str2 is %s", str1, str2);
  return 0;
}

Kunjungi berikut ini untuk informasi lebih lanjut tentang referensi ke penunjuk:

Seperti yang saya katakan, pointer ke referensi tidak mungkin. Coba program berikut ini:

#include <iostream>
using namespace std;

int main()
{
   int x = 10;
   int *ptr = &x;
   int &*ptr1 = ptr;
}
Destructor
sumber
16

Saya menggunakan referensi kecuali saya membutuhkan salah satu dari ini:

  • Null pointer dapat digunakan sebagai nilai sentinel, seringkali merupakan cara murah untuk menghindari fungsi yang berlebihan atau penggunaan bool.

  • Anda dapat melakukan aritmatika pada sebuah pointer. Sebagai contoh,p += offset;

tapir
sumber
5
Anda dapat menulis di &r + offsetmana rdinyatakan sebagai referensi
MM
15

Ada satu perbedaan mendasar antara pointer dan referensi yang saya tidak melihat ada yang disebutkan: referensi memungkinkan semantik referensi lewat dalam argumen fungsi. Pointer, meskipun tidak terlihat pada awalnya tidak: mereka hanya menyediakan semantik nilai-demi-nilai. Ini telah dijelaskan dengan sangat baik dalam artikel ini .

Salam, & rzej

Andrzej
sumber
1
Referensi dan petunjuk keduanya pegangan. Keduanya memberi Anda semantik tempat objek Anda dilewatkan dengan referensi, tetapi pegangan disalin. Tidak ada perbedaan. (Ada juga cara lain untuk memiliki pegangan, seperti kunci untuk mencari di kamus)
Ben Voigt
Dulu saya juga berpikir seperti ini. Tetapi lihat artikel terkait yang menjelaskan mengapa tidak demikian.
Andrzej
2
@Andrzj: Itu hanya versi yang sangat panjang dari satu kalimat dalam komentar saya: Pegangan disalin.
Ben Voigt
Saya perlu penjelasan lebih lanjut tentang "Pegangan ini disalin". Saya mengerti beberapa ide dasar tetapi saya pikir secara fisik referensi dan pointer menunjuk kedua lokasi memori variabel. Apakah ini seperti alias menyimpan variabel nilai dan memutakhirkannya sebagai nilai variabel berubah atau lainnya? Saya pemula, dan tolong jangan menandainya sebagai pertanyaan bodoh.
Asim
1
@Andrzej Salah. Dalam kedua kasus, nilai by pass terjadi. Referensi dilewatkan oleh nilai dan pointer dilewatkan oleh nilai. Mengatakan sebaliknya membingungkan pemula.
Miles Rout
14

Dengan risiko menambah kebingungan, saya ingin memasukkan beberapa input, saya yakin itu sebagian besar tergantung pada bagaimana kompiler mengimplementasikan referensi, tetapi dalam kasus gcc gagasan bahwa referensi hanya dapat menunjuk ke variabel di stack sebenarnya tidak benar, ambil contoh ini:

#include <iostream>
int main(int argc, char** argv) {
    // Create a string on the heap
    std::string *str_ptr = new std::string("THIS IS A STRING");
    // Dereference the string on the heap, and assign it to the reference
    std::string &str_ref = *str_ptr;
    // Not even a compiler warning! At least with gcc
    // Now lets try to print it's value!
    std::cout << str_ref << std::endl;
    // It works! Now lets print and compare actual memory addresses
    std::cout << str_ptr << " : " << &str_ref << std::endl;
    // Exactly the same, now remember to free the memory on the heap
    delete str_ptr;
}

Yang menghasilkan ini:

THIS IS A STRING
0xbb2070 : 0xbb2070

Jika Anda melihat bahkan alamat memori persis sama, artinya referensi berhasil menunjuk ke variabel di heap! Sekarang jika Anda benar-benar ingin menjadi aneh, ini juga berfungsi:

int main(int argc, char** argv) {
    // In the actual new declaration let immediately de-reference and assign it to the reference
    std::string &str_ref = *(new std::string("THIS IS A STRING"));
    // Once again, it works! (at least in gcc)
    std::cout << str_ref;
    // Once again it prints fine, however we have no pointer to the heap allocation, right? So how do we free the space we just ignorantly created?
    delete &str_ref;
    /*And, it works, because we are taking the memory address that the reference is
    storing, and deleting it, which is all a pointer is doing, just we have to specify
    the address with '&' whereas a pointer does that implicitly, this is sort of like
    calling delete &(*str_ptr); (which also compiles and runs fine).*/
}

Yang menghasilkan ini:

THIS IS A STRING

Oleh karena itu referensi ADALAH pointer di bawah kap, mereka berdua hanya menyimpan alamat memori, di mana alamat menunjuk tidak relevan, apa yang menurut Anda akan terjadi jika saya memanggil std :: cout << str_ref; SETELAH menelepon hapus & str_ref? Yah, jelas itu mengkompilasi baik-baik saja, tetapi menyebabkan kesalahan segmentasi saat runtime karena tidak lagi menunjuk pada variabel yang valid, kami pada dasarnya memiliki referensi rusak yang masih ada (sampai jatuh keluar dari ruang lingkup), tetapi tidak berguna.

Dengan kata lain, referensi tidak lain adalah sebuah pointer yang memiliki mekanisme pointer diabstraksi, membuatnya lebih aman dan lebih mudah digunakan (tidak ada matematika pointer yang tidak disengaja, tidak ada pencampuran '.' Dan '->', dll.), Dengan asumsi Anda jangan coba-coba omong kosong seperti contoh saya di atas;)

Sekarang terlepas dari bagaimana kompiler menangani referensi, ia akan selalu memiliki semacam pointer di bawah kap, karena referensi harus merujuk ke variabel tertentu pada alamat memori tertentu agar dapat berfungsi seperti yang diharapkan, tidak ada jalan untuk mengatasi hal ini (karenanya istilah 'referensi').

Satu-satunya aturan utama yang penting untuk diingat dengan referensi adalah bahwa mereka harus didefinisikan pada saat deklarasi (dengan pengecualian referensi di header, dalam hal ini harus didefinisikan dalam konstruktor, setelah objek yang dikandungnya adalah dibangun sudah terlambat untuk mendefinisikannya).

Ingat, contoh saya di atas hanya itu, contoh menunjukkan apa referensi itu, Anda tidak akan pernah ingin menggunakan referensi dengan cara itu! Untuk penggunaan referensi yang benar, ada banyak jawaban di sini yang sudah menyentuh kepala

Tory
sumber
14

Perbedaan lain adalah bahwa Anda dapat memiliki pointer ke tipe void (dan itu berarti pointer ke apa pun) tetapi referensi untuk void dilarang.

int a;
void * p = &a; // ok
void & p = a;  //  forbidden

Saya tidak bisa mengatakan saya sangat senang dengan perbedaan khusus ini. Saya lebih suka itu akan diizinkan dengan referensi makna apa pun dengan alamat dan perilaku yang sama untuk referensi. Ini akan memungkinkan untuk mendefinisikan beberapa ekuivalen fungsi perpustakaan C seperti memcpy menggunakan referensi.

Kriss
sumber
13

Juga, referensi yang merupakan parameter untuk fungsi yang digariskan dapat ditangani secara berbeda dari pointer.

void increment(int *ptrint) { (*ptrint)++; }
void increment(int &refint) { refint++; }
void incptrtest()
{
    int testptr=0;
    increment(&testptr);
}
void increftest()
{
    int testref=0;
    increment(testref);
}

Banyak kompiler ketika inlining versi satu pointer benar-benar akan memaksa menulis ke memori (kami mengambil alamat secara eksplisit). Namun, mereka akan meninggalkan referensi dalam register yang lebih optimal.

Tentu saja, untuk fungsi-fungsi yang tidak digarisbawahi pointer dan referensi menghasilkan kode yang sama dan selalu lebih baik untuk lulus nilai intrinsik daripada dengan referensi jika mereka tidak dimodifikasi dan dikembalikan oleh fungsi.

Adisak
sumber
11

Penggunaan referensi lain yang menarik adalah untuk menyediakan argumen default dari tipe yang ditentukan pengguna:

class UDT
{
public:
   UDT() : val_d(33) {};
   UDT(int val) : val_d(val) {};
   virtual ~UDT() {};
private:
   int val_d;
};

class UDT_Derived : public UDT
{
public:
   UDT_Derived() : UDT() {};
   virtual ~UDT_Derived() {};
};

class Behavior
{
public:
   Behavior(
      const UDT &udt = UDT()
   )  {};
};

int main()
{
   Behavior b; // take default

   UDT u(88);
   Behavior c(u);

   UDT_Derived ud;
   Behavior d(ud);

   return 1;
}

Rasa standar menggunakan referensi 'binding const untuk aspek referensi sementara'.

Don Wakefield
sumber
11

Program ini dapat membantu dalam memahami jawaban pertanyaan. Ini adalah program sederhana dari referensi "j" dan pointer "ptr" yang menunjuk ke variabel "x".

#include<iostream>

using namespace std;

int main()
{
int *ptr=0, x=9; // pointer and variable declaration
ptr=&x; // pointer to variable "x"
int & j=x; // reference declaration; reference to variable "x"

cout << "x=" << x << endl;

cout << "&x=" << &x << endl;

cout << "j=" << j << endl;

cout << "&j=" << &j << endl;

cout << "*ptr=" << *ptr << endl;

cout << "ptr=" << ptr << endl;

cout << "&ptr=" << &ptr << endl;
    getch();
}

Jalankan program dan lihat hasilnya dan Anda akan mengerti.

Juga, luangkan 10 menit dan tonton video ini: https://www.youtube.com/watch?v=rlJrrGV0iOg

Arlene Batada
sumber
11

Saya merasa masih ada hal lain yang belum dibahas di sini.

Tidak seperti pointer, referensi secara sintaksis setara dengan objek yang dirujuk, yaitu operasi apa pun yang dapat diterapkan pada objek bekerja untuk referensi, dan dengan sintaks yang sama persis (pengecualian tentu saja inisialisasi).

Meskipun ini mungkin tampak dangkal, saya percaya properti ini sangat penting untuk sejumlah fitur C ++, misalnya:

  • Templat . Karena parameter template diketik bebek, sifat sintaksis suatu tipe adalah yang terpenting, sehingga sering kali templat yang sama dapat digunakan dengan keduanya Tdan T&.
    (atau std::reference_wrapper<T>yang masih bergantung pada pemeran implisit T&)
    Template yang mencakup keduanya T&dan T&&bahkan lebih umum.

  • Lvalues . Pertimbangkan pernyataan str[0] = 'X';Tanpa referensi itu hanya akan berfungsi untuk c-string ( char* str). Mengembalikan karakter dengan referensi memungkinkan kelas yang ditentukan pengguna memiliki notasi yang sama.

  • Salin konstruktor . Secara sintaksis masuk akal untuk meneruskan objek untuk menyalin konstruktor, dan bukan penunjuk ke objek. Tetapi tidak ada cara bagi copy constructor untuk mengambil objek berdasarkan nilai - itu akan menghasilkan panggilan rekursif ke copy constructor yang sama. Ini menjadikan referensi sebagai satu-satunya pilihan di sini.

  • Operator kelebihan beban . Dengan referensi dimungkinkan untuk memperkenalkan tipuan ke panggilan operator - katakan, operator+(const T& a, const T& b)sambil mempertahankan notasi infiks yang sama. Ini juga berfungsi untuk fungsi kelebihan beban reguler.

Poin-poin ini memberdayakan sebagian besar C ++ dan perpustakaan standar sehingga ini adalah properti referensi yang cukup utama.

Ap31
sumber
" cast implisit " cast adalah sintaks konstruksi, itu ada dalam tata bahasa; para pemeran selalu eksplisit
curiousguy
9

Ada perbedaan non-teknis yang sangat penting antara pointer dan referensi: Argumen yang diteruskan ke fungsi oleh pointer jauh lebih terlihat daripada argumen yang dilewatkan ke fungsi oleh referensi non-const. Sebagai contoh:

void fn1(std::string s);
void fn2(const std::string& s);
void fn3(std::string& s);
void fn4(std::string* s);

void bar() {
    std::string x;
    fn1(x);  // Cannot modify x
    fn2(x);  // Cannot modify x (without const_cast)
    fn3(x);  // CAN modify x!
    fn4(&x); // Can modify x (but is obvious about it)
}

Kembali di C, panggilan yang sepertinya fn(x)hanya bisa diteruskan oleh nilai, jadi itu pasti tidak dapat memodifikasi x; untuk memodifikasi argumen, Anda harus melewati sebuah pointer fn(&x). Jadi, jika argumen tidak diawali oleh &Anda tahu itu tidak akan diubah. (Kebalikannya, &berarti dimodifikasi, itu tidak benar karena Anda terkadang harus melewati struktur read-only besar dengan constpointer.)

Beberapa berpendapat bahwa ini adalah fitur yang sangat berguna ketika membaca kode, bahwa parameter pointer harus selalu digunakan untuk parameter yang dapat dimodifikasi daripada non- constreferensi, bahkan jika fungsi tidak pernah mengharapkan a nullptr. Artinya, orang-orang berpendapat bahwa tanda tangan fungsi seperti di fn3()atas tidak boleh diizinkan. Pedoman gaya C ++ Google adalah contohnya.

Arthur Tacca
sumber
8

Mungkin beberapa metafora akan membantu; Dalam konteks ruang layar desktop Anda -

  • Referensi mengharuskan Anda untuk menentukan jendela aktual.
  • Pointer membutuhkan lokasi sepotong ruang di layar yang Anda yakinkan akan berisi nol atau lebih contoh jenis jendela itu.
George R
sumber
6

Perbedaan antara pointer dan referensi

Pointer dapat diinisialisasi ke 0 dan referensi tidak. Bahkan, referensi juga harus merujuk ke objek, tetapi sebuah pointer bisa menjadi null pointer:

int* p = 0;

Tetapi kita tidak dapat memiliki int& p = 0;dan juga int& p=5 ;.

Bahkan untuk melakukannya dengan benar, kita harus mendeklarasikan dan mendefinisikan objek pada awalnya, lalu kita dapat membuat referensi ke objek itu, sehingga implementasi yang benar dari kode sebelumnya adalah:

Int x = 0;
Int y = 5;
Int& p = x;
Int& p1 = y;

Poin penting lainnya adalah kita dapat membuat deklarasi pointer tanpa inisialisasi namun tidak ada hal seperti itu dapat dilakukan dalam kasus referensi yang harus membuat referensi selalu ke variabel atau objek. Namun penggunaan pointer seperti itu berisiko sehingga secara umum kami memeriksa apakah pointer sebenarnya menunjuk ke sesuatu atau tidak. Dalam hal referensi tidak diperlukan pemeriksaan seperti itu, karena kita sudah tahu bahwa merujuk ke objek selama deklarasi adalah wajib.

Perbedaan lain adalah bahwa pointer dapat menunjuk ke objek lain namun referensi selalu merujuk ke objek yang sama, mari kita ambil contoh ini:

Int a = 6, b = 5;
Int& rf = a;

Cout << rf << endl; // The result we will get is 6, because rf is referencing to the value of a.

rf = b;
cout << a << endl; // The result will be 5 because the value of b now will be stored into the address of a so the former value of a will be erased

Poin lain: Ketika kita memiliki templat seperti templat STL, templat kelas semacam itu akan selalu mengembalikan referensi, bukan penunjuk, untuk memudahkan membaca atau menetapkan nilai baru menggunakan operator []:

Std ::vector<int>v(10); // Initialize a vector with 10 elements
V[5] = 5; // Writing the value 5 into the 6 element of our vector, so if the returned type of operator [] was a pointer and not a reference we should write this *v[5]=5, by making a reference we overwrite the element by using the assignment "="
dhokar.w
sumber
1
Kita masih bisa memilikinya const int& i = 0.
Revolver_Ocelot
1
Dalam hal ini referensi hanya akan digunakan dalam membaca kita tidak dapat mengubah referensi const ini bahkan menggunakan "const_cast" karena "const_cast" hanya menerima pointer bukan referensi.
dhokar.w
1
const_cast bekerja dengan referensi dengan cukup baik: coliru.stacked-crooked.com/a/eebb454ab2cfd570
Revolver_Ocelot
1
Anda membuat gips untuk referensi bukan casting referensi coba ini; const int & i =; const_cast <int> (i); saya mencoba untuk membuang keteguhan referensi untuk memungkinkan penulisan dan penugasan nilai baru untuk referensi tetapi ini tidak mungkin. tolong fokus !!
dhokar.w
5

Perbedaannya adalah bahwa variabel pointer non-konstan (jangan dikacaukan dengan pointer ke konstan) dapat diubah pada suatu waktu selama pelaksanaan program, memerlukan semantik pointer untuk digunakan operator (&, *), sementara referensi dapat ditetapkan pada inisialisasi hanya (itu sebabnya Anda dapat mengaturnya dalam daftar penginisialisasi konstruktor saja, tetapi tidak entah bagaimana cara lain) dan menggunakan semantik mengakses nilai biasa. Pada dasarnya referensi diperkenalkan untuk memungkinkan dukungan bagi operator kelebihan seperti yang saya baca di beberapa buku yang sangat tua. Seperti seseorang yang dinyatakan dalam utas ini - pointer dapat diatur ke 0 atau nilai apa pun yang Anda inginkan. 0 (NULL, nullptr) berarti bahwa pointer diinisialisasi dengan nol. Ini adalah kesalahan untuk dereference null pointer. Tetapi sebenarnya pointer mungkin berisi nilai yang tidak menunjuk ke beberapa lokasi memori yang benar. Referensi pada gilirannya mencoba untuk tidak memungkinkan pengguna untuk menginisialisasi referensi ke sesuatu yang tidak dapat direferensikan karena fakta bahwa Anda selalu memberikan nilai dari jenis yang benar untuk itu. Meskipun ada banyak cara untuk membuat variabel referensi diinisialisasi ke lokasi memori yang salah - lebih baik bagi Anda untuk tidak menggali lebih dalam rincian ini. Pada level mesin, baik pointer maupun referensi bekerja secara seragam - melalui pointer. Katakanlah dalam referensi penting adalah gula sintaksis. referensi nilai berbeda untuk ini - mereka secara alami tumpukan / tumpukan objek. Meskipun ada banyak cara untuk membuat variabel referensi diinisialisasi ke lokasi memori yang salah - lebih baik bagi Anda untuk tidak menggali lebih dalam rincian ini. Pada level mesin, baik pointer maupun referensi bekerja secara seragam - melalui pointer. Katakanlah dalam referensi penting adalah gula sintaksis. referensi nilai berbeda untuk ini - mereka secara alami tumpukan / tumpukan objek. Meskipun ada banyak cara untuk membuat variabel referensi diinisialisasi ke lokasi memori yang salah - lebih baik bagi Anda untuk tidak menggali lebih dalam rincian ini. Pada level mesin, baik pointer maupun referensi bekerja secara seragam - melalui pointer. Katakanlah dalam referensi penting adalah gula sintaksis. referensi nilai berbeda untuk ini - mereka secara alami tumpukan / tumpukan objek.

Zorgiev
sumber
4

dalam kata-kata sederhana, kita dapat mengatakan referensi adalah nama alternatif untuk variabel sedangkan, pointer adalah variabel yang menyimpan alamat variabel lain. misalnya

int a = 20;
int &r = a;
r = 40;  /* now the value of a is changed to 40 */

int b =20;
int *ptr;
ptr = &b;  /*assigns address of b to ptr not the value */
Sadhana Singh
sumber