Koje su razlike između pokazivača i referentne varijable u C ++?

Znam da su veze sintaktički šećer, tako da je kod lakše čitati i pisati.

Ali koje su razlike?


Sažetak odgovora i veza u nastavku:

  1. Pokazivač se može preraspodijeliti bilo koji broj puta, a veza se ne može ponovno dodijeliti nakon vezanja.
  2. Pokazivači ne mogu pokazivati ​​nigdje ( NULL ), dok se veza uvijek odnosi na objekt.
  3. Ne možete koristiti adresu veze, kao što možete, pokazivače.
  4. Ne postoji "referentna aritmetika" (ali možete uzeti adresu objekta na koji link pokazuje i izvoditi aritmetiku pokazivača na njemu, kao u + 5 ).

Da izbrišete zabludu:

C ++ standard je vrlo oprezan da ne diktira kako prevodilac može implementirati reference, ali svaki C ++ prevodilac implementira reference kao pokazivače. To jest, izjava kao:

 int  = i; 

ako nije u potpunosti optimiziran , dodjeljuje istu količinu memorije kao pokazivač i smješta adresu i u ovo spremište.

Dakle, pokazivač i veza koriste istu količinu memorije.

Kao opće pravilo,

  • Upotrijebite reference u parametrima funkcija i tipovima vraćanja kako biste osigurali korisna i samo-dokumentirajuća sučelja.
  • Koristite pokazivače za implementaciju algoritama i struktura podataka.

Zanimljivo čitanje:

2802
11 сент. set prakash 11. rujna 2008-09-11 23:03 '08 u 23:03 2008-09-11 23:03
@ 37 odgovora
  • 1
  • 2
  • Pokazivač se može preraspodijeliti:

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

    Veza ne može i ne smije biti dodijeljena tijekom inicijalizacije:

     int x = 5; int y = 6; int  = x; 
  • Pokazivač ima svoju adresu i veličinu memorije u stogu (4 bajta na x86), dok veza ima istu adresu memorije (s izvornom varijablom), ali i zauzima malo mjesta na stogu. Budući da veza ima istu adresu kao i izvorna varijabla, sigurno je da je veza povezana s drugim nazivom iste varijable. Napomena. Ono na što pokazivač pokazuje može biti na stog ili na hrpi. Ista veza. Moja tvrdnja u ovoj izjavi nije da bi pokazivač trebao pokazivati ​​na stog. Pokazivač je jednostavno varijabla koja sadrži adresu memorije. Ova je varijabla na stogu. Budući da veza ima svoj prostor stog, a adresa je ista kao i varijabla na koju se odnosi. Pročitajte više na stack vs heap . To znači da postoji stvarna adresa veze koju vam prevodilac neće reći.

     int x = 0; int  = x; int *p =  int *p2 =  assert(p == p2); 
  • Možda imate pokazivače na pokazivače na pokazivače koji nude dodatne razine neizravnosti. Dok linkovi nude samo jednu razinu neizravnosti.

     int x = 0; int y = 0; int *p =  int *q =  int **pp =  pp =  = q **pp = 4; assert(y == 4); assert(x == 0); 
  • Pokazivač se može dodijeliti izravno nullptr , ali veza ne može. Ako se dovoljno trudite i znate kako to učiniti, možete napraviti adresu veze nullptr . Slično tome, ako se dovoljno trudite, možete imati vezu na pokazivač, a onda ta veza može sadržavati 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 
  • Pokazivači se mogu ponavljati preko niza, možete upotrijebiti ++ za prelazak na sljedeći element na koji ukazuje pokazivač, i + 4 za prelazak na 5. stavku. To je bez obzira na veličinu na koju je objekt ukazao.

  • Pokazivač se mora dereferencirati pomoću * za pristup memorijskoj adresi na koju pokazuje, dok se veza može koristiti izravno. Pokazivač na klasu / strukturu koristi -> da im pristupi, dok veza koristi . ,

  • Pokazivač je varijabla koja sadrži adresu memorije. Bez obzira na to kako se ova veza izvodi, veza ima istu adresu memorije kao i element na koji se odnosi.

  • Veze se ne mogu popunjavati u niz, a pokazivači mogu biti (navodi ih korisnik @litb)

  • Reference na Const mogu biti povezane s privremenim. Pokazivači ne mogu (ne posredno):

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

    Time se čini sigurnijim za upotrebu u popisima argumenata itd.

1469
11 сент. Odgovor je dao Brian R. Bondy 11 sep. 2008-09-11 23:08 '08 u 23:08 2008-09-11 23:08

Što je C ++ referenca (za C programere)

Veza se može smatrati konstantnim pokazivačem (ne smije se pomiješati s pokazivačem na konstantnu vrijednost!) S automatskim usmjeravanjem, tj. kompajler će za vas koristiti operator * .

Sve veze moraju biti inicijalizirane s ne-nultom vrijednosti, u suprotnom kompilacija neće uspjeti. Nemoguće je dobiti adresu veze - umjesto toga, adresa operator vraća adresu referentne vrijednosti - i nije moguće napraviti aritmetiku veze.

C programeri možda ne vole C ++ reference, jer neće biti očitije ako dođe do neizravnosti ili ako je argument proslijeđen vrijednosti ili pokazivačem, bez gledanja potpisa funkcija.

C ++ programeri možda ne vole koristiti pokazivače, jer se smatraju nesigurnima, iako veze zapravo nisu sigurnije od uobičajenih pokazivača, osim u najtrivijalnijim slučajevima - nemaju praktičnost automatskog usmjeravanja i nose različitu semantičku konotaciju.

Razmotrite sljedeću izjavu iz C ++ Često postavljanih pitanja :

border=0

Čak i ako se veza često izvodi pomoću adrese u osnovnom jeziku asemblera, nemojte misliti na vezu kao smiješni pokazivač na objekt. Veza je objekt. on nije pokazivač na objekt, niti kopija objekta. Ovo je objekt.

Ali ako je veza doista bila objekt, kako mogu postojati prekinute veze? Na neupravljanim jezicima nije moguće da veze budu "sigurnije" od pokazivača - u pravilu, to jednostavno nije način da se pouzdano označe vrijednosti granica opsega!

Zašto sam pronašao korisne linkove u C ++

Na temelju C-pozadine, C ++ reference mogu izgledati kao pomalo glup koncept, ali ih ipak trebate koristiti umjesto pokazivača gdje je to moguće: automatska indirekcija je prikladna, a veze postaju osobito korisne kada radite s RAII - ali ne zato što bilo koje percipirane sigurnosne prednosti, nego zbog činjenice da pismo čini idiomskim kodom manje neugodno.

RAII je jedan od središnjih koncepata C ++-a, ali netrivialno interagira s semantikom kopiranja. Prosljeđivanje objekata po referenci omogućuje vam da izbjegnete te probleme, budući da se kopiranje ne izvodi. Ako linkovi nisu prisutni na ovom jeziku, morat ćete umjesto toga koristiti pokazivače, koji su teže koristiti, čime se krši načelo razvoja jezika da bi rješenje s boljom praksom trebalo biti jednostavnije od alternativa.

324
28 февр. Odgovor dao Christoph 28. veljače 2009-02-28 00:26 '09 u 0:26 2009-02-28 00:26

Ako želite biti istinski pedantni, postoji jedna stvar koju možete učiniti s vezom koju ne možete učiniti s pokazivačem: produžite vijek trajanja privremenog objekta. U C ++, ako vežete const referencu na privremeni objekt, vijek trajanja ovog objekta postaje vijek trajanja veze.

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

U ovom primjeru s3_copy kopira privremeni objekt koji je rezultat ulančavanja. Dok s3_reference u biti postaje privremeni objekt. To je zapravo veza na privremeni objekt, koji sada ima isti vijek trajanja kao i veza.

Ako pokušate ovo bez const , to ne bi trebalo kompilirati. Ne možete povezati nepokretnu vezu s privremenim objektom i ne možete prihvatiti njegovu adresu u tom pogledu.

167
12 сент. Odgovori Matt Price 12 ruj. 2008-09-12 00:43 '08 u 0:43 2008-09-12 00:43

Suprotno uvriježenom mišljenju, moguće je imati NULL vezu.

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

Naravno, to je mnogo teže povezati s vezom, ali ako se nosite s tim, rastrgat ćete kosu, pokušavajući je pronaći. C + + linkovi nisu inherentno sigurni!

Tehnički, ovo je nevažeća referenca , a ne nula referenca. C ++ ne podržava null linkove kao koncept, kao što možete pronaći na drugim jezicima. Postoje i druge nevažeće veze. Bilo koja nevažeća veza povećava raspon nedefiniranih ponašanja , kao i korištenje nevažećeg pokazivača.

Stvarna pogreška je u dereferenciranju NULL pokazivača prije dodjeljivanja veze. Ali ja ne znam bilo kompilatora koji će generirati bilo kakve pogreške pod ovim uvjetom - greška se distribuira do točke dalje u kodu. To ovaj problem čini tako podmuklim. U većini slučajeva, ako igrate NULL pokazivač, kunete se pravo na ovom mjestu i ne zahtijeva puno otklanjanja pogrešaka da biste to shvatili.

Moj gore navedeni primjer je kratak i izmišljen. Ovo je realniji primjer.

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

Želim ponoviti da je jedini način da dobijete null link pogrešan kod, i čim ga dobijete, dobivate nedefinirano ponašanje. Nikada nema smisla provjeravati nultu referencu; na primjer, možete pokušati if(> , ali prevodilac može optimizirati izjavu iz postojanja! Valjana referenca nikada ne može biti NULL, tako da je uspoređivanje uvijek lažno iz reprezentacije prevoditelja, a možete slobodno isključiti stavku if kao mrtvi kod - to je suština ponašanja nedefiniranih.

Pravilan način izbjegavanja problema je izbjegavanje dereferenciranja null pointera za stvaranje reference. Ovo je automatizirani način za to.

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

Za stariji pogled na ovaj problem za nekoga tko ima najbolje vještine pisanja, vidi Null Reference iz Jim Hyslopa i Herb Suttera.

Za još jedan primjer opasnosti od dereferenciranja null pointera, pogledajte Prikaz ponašanja nedefiniranih prilikom pokušaja prijenosa koda na drugu Raymond Chen platformu .

114
12 сент. Odgovor koji je dao Mark Ransom 12. rujna 2008-09-12 00:06 '08 u 0:06 2008-09-12 00:06

Osim sintaktičkog šećera, veza je i const pointer (ne pokazivač na const ). Morate utvrditi na što se ovo odnosi kada deklarirate referentnu varijablu, a kasnije je ne možete promijeniti.

Ažuriranje: sada kada ponovno razmislim o tome, postoji važna razlika.

Ciljni konstantni pokazivač može se zamijeniti uzimanjem njegove adrese i uporabom const konstante.

Ciljanu vezu nije moguće zamijeniti ni na koji način ispod UB-a.

To bi trebalo omogućiti kompajleru da napravi veliku optimizaciju prema referenci.

111
11 сент. odgovor je dat Arkadiy 11 sep. 2008-09-11 23:07 '08 u 23:07 2008-09-11 23:07

Zaboravili ste najvažniji dio:

pristup članovima s pokazivačima koristi ->
pristup članovima s vezama koristi .

foo.bar očigledno bolji od foo->bar na isti način na koji je vi jasno superiorniji od Emacsa : -)

105
12 сент. Odgovor daje Orion Edwards 12. rujna. 2008-09-12 01:10 '08 u 1:10 am 2008-09-12 01:10

Zapravo, veza ne izgleda kao pokazivač.

Kompajler pohranjuje "reference" na varijable, povezujući ime s memorijskom adresom; da je njegov zadatak prevesti bilo koje ime varijable u memorijsku adresu prilikom kompajliranja.

Kada stvorite vezu, kompajleru recite da dodjeljujete drugo ime varijable pokazivača; zašto veze ne mogu "ukazati na nulu", jer varijabla ne može biti, a ne biti.

Pokazivači su varijabilni; oni sadrže adresu neke druge varijable ili mogu biti nula. Važno je da pokazivač ima vrijednost, a veza ima samo varijablu na koju se odnosi.

Sada za neko objašnjenje stvarnog koda:

 int a = 0; int b = a; 

Ovdje ne stvarate drugu varijablu koja pokazuje na a ; jednostavno dodajete drugo ime sadržaju memorije koji sadrži vrijednost a . Ovo sjećanje sada ima dva imena: a i b , i može se riješiti bilo kojim imenom.

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

Kada se pozove funkcija, prevodilac obično generira memorijske prostore za kopiranje argumenata. Funkcionalni potpis definira prostore koje treba stvoriti i daje ime koje će se koristiti za te prostore. Proglašenje parametra kao reference jednostavno govori kompajleru da koristi prostor s varijablom ulaznom varijablom umjesto da dodijeli novi memorijski prostor tijekom poziva metode. Može se činiti čudnim reći da će vaša funkcija izravno manipulirati varijablom deklariranom u području poziva, ali zapamtite da pri izvršavanju prevedenog koda više nema prostora; tu je samo ravna memorija, a vaš kod funkcije može manipulirati svim varijablama.

Sada može biti trenutaka kada vaš kompilator možda neće znati referencu kada kompajlira, na primjer, kada koristi varijablu extern. Prema tome, veza može ili ne mora biti implementirana kao pokazivač u osnovnom kodu. Ali u primjerima koje sam vam dao, najvjerojatnije neće biti implementiran s pokazivačem.

63
19 сент. odgovor je dao Vincent Robert na 19. rujna 2008-09-19 15:23 '08 u 15:23 2008-09-19 15:23

Veze su vrlo slične pokazivačima, ali su posebno kreirane kako bi optimizirale prevodioce.

  • Reference su dizajnirane na takav način da kompajler uvelike pojednostavljuje praćenje referentnih pseudonima, koje su varijable. Vrlo su važne dvije važne značajke: nema "referentne aritmetike" i nema preraspodjele referenci. Oni dopuštaju prevodiocu da shvati koje veze sadrže pseudonime, koje su varijable u vrijeme prevođenja.
  • Veze se mogu odnositi na varijable koje nemaju adrese memorije, na primjer, koje prevodilac odabere za ulazak u registre. Ako uzmete adresu lokalne varijable, kompajler je vrlo teško staviti u registar.

Kao primjer:

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

Optimizacijski prevodilac može razumjeti da pristupamo [0] i [1] prilično hrpi. Bilo bi poželjno optimizirati algoritam tako da:

 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 } 

Da biste to učinili, potrebno je dokazati da tijekom poziva ništa ne može promijeniti polje [1]. Lako je to učiniti. Ja sam barem 2, tako da niz [i] nikada ne može upućivati ​​na niz [1]. maybeModify () je dodijeljen a0 kao referenca (polje alijasa [0]). Budući da ne postoji "referentna" aritmetika, kompajler jednostavno mora dokazati da je to moguće: Modify nikada ne dobiva adresu x, i dokazao je da ništa ne mijenja polje [1].

Također mora dokazati da ne postoji način na koji bi budući poziv mogao čitati / pisati [0], dok imamo privremenu kopiju registra u a0. To je često trivijalno za dokazivanje, jer je u mnogim slučajevima očito da se ta referenca nikada ne pohranjuje u stalnoj strukturi, kao što je primjer klase.

Sada učinite isto s pokazivačima.

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

Ponašanje je isto; tek sada je mnogo teže dokazati da, možda, Modify nikada ne modificira niz [1], jer smo mu već pokazali pokazivač; mačka je izašla iz torbe. Sada mora napraviti mnogo složeniji dokaz: statička analiza može izmijeniti kako bi dokazala da nikad ne piše u x + 1. Također mora dokazati da nikada ne eliminira pokazivač koji se može odnositi na niz [0] koliko je teško.

Moderni kompilatori postaju sve bolji i bolji sa statičkom analizom, ali uvijek je lijepo pomoći im i koristiti linkove.

Naravno, uz iznimku takvih pametnih optimizacija, kompilatori će se uistinu odnositi na reference u pokazivačima kada je to potrebno.

EDIT: Pet godina nakon objavljivanja ovog odgovora, pronašao sam stvarnu tehničku razliku, gdje se veze razlikuju od drugog načina gledanja na isti koncept adresiranja. Veze mogu promijeniti životni vijek privremenih objekata na takav način da pokazivači ne mogu.

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

Obično na kraju izraza, obično se uništavaju privremeni objekti, kao što su oni kreirani pozivom createF(5) . Međutim, povezivanjem tog objekta s referencom, ref , C ++ će produžiti vijek trajanja ovog privremenog objekta dok ref ne nadilazi.

62
01 сент. Odgovor se daje Cort Ammon 01 sep. 2013-09-01 06:44 '13 u 6:44 2013-09-01 06:44

Veza nikad ne može biti NULL .

37
11 сент. Odgovor je dao RichS 11. rujna. 2008-09-11 23:12 '08 u 23:12 2008-09-11 23:12

Iako se i linkovi i pokazivači koriste za neizravan pristup drugoj vrijednosti, postoje dvije važne razlike između veza i pokazivača. Prvo, veza se uvijek odnosi na objekt: pogreška je u definiciji veze bez inicijalizacije. Ponašanje dodjele je druga važna razlika: Dodjela veze zamjenjuje objekt na koji se povezuje; ne provjerava ponovno vezu s drugim objektom. Nakon inicijalizacije, veza se uvijek odnosi na isti osnovni objekt.

Razmotrite ova dva dijela programa. U prvoj dodijelimo jedan pokazivač drugom:

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

Nakon zadatka, ival, objekt adresiran pi ostaje nepromijenjen. Dodjela mijenja vrijednost pi, ukazujući na drugi objekt. Sada razmotrite sličan program koji dodjeljuje dvije veze:

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