Kokie yra rodyklės kintamojo ir C + + nuorodos kintamojo skirtumai?

Žinau, kad nuorodos yra sintaksinis cukrus, todėl kodą lengviau skaityti ir rašyti.

Bet kokie yra skirtumai?


Atsakymų ir nuorodų santrauka:

  1. Rodyklę galima pakartotinai paskirti keletą kartų, o nuoroda negali būti perkelta po įpareigojimo.
  2. Rodyklės negali nukreipti bet kur ( NULL ), o nuoroda visada reiškia objektą.
  3. Jūs negalite naudoti nuorodos adreso, kaip jūs galite, rodykles.
  4. Nėra „atskaitos aritmetikos“ (tačiau galite pasirinkti objekto, į kurį nukreipia nuorodą, adresą ir atlikti rodyklės aritmetiką, kaip + 5 ).

Norėdami išvalyti klaidą:

C ++ standartas yra labai atsargus, kad nenurodytų, kaip kompiliatorius gali įgyvendinti nuorodas, tačiau kiekvienas C ++ kompiliatorius naudoja nuorodas kaip rodykles. Tai yra:

 int  = i; 

jei jis nėra visiškai optimizuotas , priskiria tą patį kiekį atminties, kaip ir rodyklė, ir įterpia adresą į šį saugyklą.

Taigi rodyklė ir nuoroda naudoja tą patį kiekį atminties.

Paprastai

  • Naudokite funkcijų parametrų ir grąžinimo tipų nuorodas, kad pateiktumėte naudingas ir savarankiškai dokumentuojamas sąsajas.
  • Naudokite nuorodas algoritmų ir duomenų struktūrų diegimui.

Įdomus skaitymas:

2802
11 сент. nustatyti prakash rugsėjo 11 d 2008-09-11 23:03 '08, 11:03 val. 2008-09-11 23:03
@ 37 atsakymai
  • 1
  • 2
  • Rodyklę galima paskirti:

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

    Nuoroda negali ir neturėtų būti priskirta inicijavimo metu:

     int x = 5; int y = 6; int  = x; 
  • Rodyklė turi savo adresą ir atminties dydį (4 baitai x86), o nuoroda turi tą patį atminties adresą (su originaliu kintamuoju), bet taip pat užima tam tikrą erdvę. Kadangi nuoroda turi tą patį adresą, kaip ir pirminis kintamasis, nuorodą galima laikyti skirtingu to paties kintamojo pavadinimu. Pastaba Ką nukreipia rodyklė gali būti ant kamino ar krūvos. Ta pati nuoroda. Mano teiginys šiame pareiškime yra ne tai, kad rodyklė turėtų nukreipti į kaminą. Rodyklė yra tik kintamasis, turintis atminties adresą. Šis kintamasis yra ant kamino. Kadangi nuoroda turi savo kamino erdvę, ir kadangi adresas yra tas pats, kurį jis nurodo. Skaitykite daugiau apie steką prieš krūvą . Tai reiškia, kad yra tikras nuorodos adresas, kurį kompiliatorius jums nesakys.

     int x = 0; int  = x; int *p =  int *p2 =  assert(p == p2); 
  • Gali būti nuorodų į nuorodas į nuorodas, kurios siūlo papildomus netiesioginius lygius. Nors nuorodos siūlo tik vieną netiesioginio lygio lygį.

     int x = 0; int y = 0; int *p =  int *q =  int **pp =  pp =  = q **pp = 4; assert(y == 4); assert(x == 0); 
  • Rodyklė gali būti priskirta tiesiogiai nullptr , bet nuoroda negali. Jei bandote pakankamai sunkiai ir žinote, kaip tai padaryti, galite nurodyti nuorodos adresą nullptr . Panašiai, jei bandote pakankamai sunkiai, galite turėti nuorodą į rodyklę, tada ši nuoroda gali turėti 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 
  • Rodyklės gali kartoti per masyvą, galite naudoti ++ norite pereiti prie kito žymiklio rodomo elemento, o + 4 - į 5 punktą. Tai nepriklausomai nuo to, kokio dydžio objektas nurodė.

  • Rodyklė turi būti nukreipta naudojant * kad pasiektumėte atminties adresą, į kurį jis nurodo, o nuoroda gali būti naudojama tiesiogiai. Rodyklė į klasę / struktūrą naudoja -> juos pasiekti, o nuoroda naudojama . .

  • Rodyklė yra kintamasis, turintis atminties adresą. Nepriklausomai nuo to, kaip ši nuoroda vykdoma, nuoroda turi tą patį atminties adresą, kaip ir elementas, į kurį jis nurodo.

  • Nuorodos negali būti įtrauktos į masyvą, o rodyklės gali būti nurodytos (@litb naudotojo)

  • Nuorodos į Const gali būti susietos su laikinu. Rodyklės negali (netiesiogiai):

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

    Dėl šios priežasties const> saugesnis naudoti argumentų sąrašuose ir tt

1469
11 сент. Atsakymą pateikė Brian R. Bondy 11 sep. 2008-09-11 23:08 '08, 11:08 val. 2008-09-11 23:08

Kas yra C ++ nuoroda (C programuotojams)

Nuoroda gali būti laikoma nuolatiniu rodikliu (negali būti painiojama su rodikliu į pastovią vertę!) Su automatiniu netiesioginiu būdu, t.y. kompiliatorius naudoja * operatorių.

Visos nuorodos turi būti inicijuotos ne nulinės vertės, kitaip kompiliavimas nepavyks. Neįmanoma gauti nuorodos adreso - vietoj to adresų operatorius grąžina pamatinės vertės adresą - ir neįmanoma padaryti nuorodų aritmetikos.

C programuotojai gali nepatikti C ++ nuorodoms, nes nebus aiškiau, ar atsiranda netiesioginis ryšys, arba jei argumentas perduodamas pagal vertę arba rodyklę, nežiūrint į funkcijų parašus.

C ++ programuotojai gali nemėgti nuorodų, nes jie laikomi nesaugiais, nors nuorodos iš tikrųjų nėra saugesnės nei įprastos nuorodos, išskyrus trivialus atvejus - jie neturi automatinio netiesioginio patogumo ir turi skirtingą semantinę reikšmę.

Apsvarstykite šį C ++ dažniausiai užduodamų klausimų pareiškimą :

border=0

Net jei nuoroda dažnai atliekama naudojant pagrindinį montuotojo kalbos kalbą, nemanau, kad nuoroda yra linksmas ieškantis rodyklės objektas. Nuoroda yra objektas. tai nėra rodyklė objektui, nei objekto kopija. Tai objektas.

Bet jei nuoroda tikrai buvo objektas, kaip galėjo būti suskaidytos nuorodos? Valdomomis kalbomis neįmanoma, kad nuorodos būtų „saugesnės“ nei rodyklės - paprastai tai tiesiog nėra būdas patikimai priskirti taikymo srities ribų vertes!

Kodėl rasti naudingų nuorodų C ++

Remiantis C fone, C + + nuorodos gali atrodyti šiek tiek kvailai, bet vis tiek turite jas naudoti vietoj rodyklių, kur tai įmanoma: automatinis netiesioginis naudojimas yra patogus, o nuorodos tampa ypač naudingos dirbant su RAII - bet ne todėl, kad bet kokie suvokiami saugumo privalumai, bet dėl ​​to, kad jie daro raidės idiomatinį kodą mažiau nepatogu.

RAII yra viena iš pagrindinių „C ++“ sąvokų, tačiau ji netriviškai bendrauja su kopijavimo semantika. Objektų perdavimas pagal nuorodą leidžia išvengti šių problemų, nes kopijavimas nevykdomas. Jei šioje kalboje nėra nuorodų, vietoj to turėsite naudoti nuorodas, kurios yra sudėtingesnės naudoti, taip pažeidžiant kalbos kūrimo principą, kuris turėtų būti paprastesnis nei alternatyva.

324
28 февр. Christofo atsakymas vasario 28 d 2009-02-28 00:26 '09 at 0:26 AM 2009-02-28 00:26

Jei norite būti tikrai pedantiškas, yra vienas dalykas, kurį galite padaryti su nuoroda, kurią negalite padaryti su rodykle: pratęsti laikino objekto tarnavimo laiką. C ++ sistemoje, jei susiejate const nuorodą į laikiną objektą, šio objekto eksploatavimo trukmė tampa ryšio trukme.

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

Šiame pavyzdyje s3_copy nukopijuoja laikiną objektą, kuris yra susiejimo rezultatas. Nors s3_reference iš esmės tampa laikinu objektu. Tai tikrai nuoroda į laikiną objektą, kuris dabar turi tą patį gyvenimą kaip ir nuoroda.

Jei bandysite tai be const , jis neturėtų sudaryti. Negalite susieti nekintamos nuorodos į laikinąjį objektą, todėl negalite sutikti su jo adresu.

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

Priešingai populiariems įsitikinimams, galima turėti NULL nuorodą.

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

Žinoma, tai yra daug sunkiau daryti su nuoroda, bet jei susidorosite su šia problema, jūs nuplėškite plaukus ir bandysite juos rasti. „C ++“ nuorodos nėra saugios!

Techniškai tai yra netinkama nuoroda , o ne nulinė nuoroda. C + + nepalaiko nulinės nuorodos kaip koncepcijos, kaip galite rasti kitomis kalbomis. Yra ir kitų negaliojančių nuorodų. Bet kuri negaliojanti nuoroda pagerina neapibrėžtą elgesį ir netinkamo rodyklės naudojimą.

Tikroji klaida prieš nukreipiant nuorodą nukenčia NULL rodyklę. Bet aš nežinau jokių kompiliatorių, kurie sukurs klaidas pagal šią sąlygą - klaida yra paskirstyta tolesniame kodo taške. Dėl to ši problema yra tokia klastinga. Daugeliu atvejų, jei žaidžiate NULL rodyklę, jūs prisiekiate teisingai šioje vietoje, ir nereikia daug derinimo, kad išsiaiškintumėte.

Mano pavyzdys aukščiau ir trumpas. Čia yra realesnis pavyzdys.

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

Noriu pakartoti, kad vienintelis būdas gauti nulinę nuorodą yra neteisingas kodas, ir kai tik jį gausite, gausite neapibrėžtą elgesį. Niekada nėra prasmės patikrinti nulinę atskaitą; pavyzdžiui, galite pabandyti, if(> , bet kompiliatorius gali optimizuoti pareiškimą iš egzistavimo! Galiojanti nuoroda niekada negali būti NULL, todėl palyginimas visada yra klaidingas iš kompiliatoriaus reprezentacijos, ir jūs galite laisvai išskirti, if sąlyga yra kaip miręs kodas - tai yra neapibrėžto elgesio esmė.

Teisingas būdas išvengti problemų yra išvengti nulinio žymeklio nukreipimo, kad būtų sukurta nuoroda. Čia yra automatizuotas būdas tai padaryti.

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

Dėl senesnės pažvelgti į šią problemą žmogui, turinčiam geriausius rašymo įgūdžius, žr. Null References iš Jim Hyslop ir Herb Sutter.

Dar vieno pavojaus, kad gali nukrypti nulinis žymeklis, pavyzdį, žr . Neapibrėžto elgesio rodymas, kai bandote perkelti kodą į kitą „ Raymond Chen“ platformą .

114
12 сент. Mark Ransom atsakymas rugsėjo 12 d 2008-09-12 00:06 '08 0:06 2008-09-12 00:06

Be sintaksinio cukraus, nuoroda yra const (ne rodyklė į const ). Turite nustatyti, ką tai reiškia, kai skelbiate nuorodos kintamąjį, ir vėliau negalite jo pakeisti.

Atnaujinimas: dabar, kai apie tai dar kartą galvoju, yra svarbus skirtumas.

Tikslus pastovus rodiklis gali būti pakeistas, atsižvelgiant į jo adresą ir naudojant pastovią konstantą.

Tikslinė nuoroda negali būti pakeista jokiu būdu žemiau UB.

Tai turėtų leisti kompiliatoriui atlikti didelį optimizavimą pagal nuorodą.

111
11 сент. atsakymas pateikiamas Arkadiy 11 sep. 2008-09-11 23:07 '08 at 23:07 pm 2008-09-11 23:07

Pamiršote svarbiausią dalį:

nario prieiga su rodyklėmis naudoja ->
prieigą prie nario su nuorodomis .

foo.bar akivaizdžiai pranašesnis už „ foo->bar “ taip pat, kaip vi aiškiai foo->bar už „ Emacs“ : -)

105
12 сент. Atsakymą pateikė „ Orion Edwards“ 12 rugsėjis. 2008-09-12 01:10 '08, 1:10 val. 2008-09-12 01:10

Tiesą sakant, nuoroda nėra rodyklė.

Kompiliatorius saugo „nuorodas“ į kintamuosius, susiejant pavadinimą su atminties adresu; kad jo užduotis yra versti bet kokį kintamojo pavadinimą į atminties adresą rengiant.

Sukūrę nuorodą, pasakykite kompiliatoriui, kad priskiriate kitą rodyklės kintamojo pavadinimą; kodėl nuorodos negali „nukreipti į nulį“, nes kintamasis negali būti ir negali būti.

Rodyklės yra įvairios; jame yra kito kintamojo adresas arba jis gali būti nulis. Svarbu, kad rodyklė turėtų vertę, o nuoroda turi tik tą kintamąjį, su kuriuo ji susijusi.

Dabar paaiškinkite tikrąjį kodą:

 int a = 0; int b = a; 

Čia nesukuriate kito kintamojo, rodančio a ; tiesiog pridedate kitą pavadinimą į atmintį, kurioje yra reikšmė a . Ši atmintis dabar turi du pavadinimus: a ir b , ir ją galima išspręsti naudojant bet kurį pavadinimą.

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

Kai funkcija vadinama, kompiliatorius paprastai generuoja atminties vietas, kad būtų galima kopijuoti argumentus. Funkcinis parašas apibrėžia vietas, kurios turi būti sukurtos, ir suteikia pavadinimą, kuris bus naudojamas šiose erdvėse. Parametro deklaravimas kaip nuoroda paprasčiausiai nurodo kompilatoriui naudoti erdvę su kintamojo įvesties kintamuoju, o ne paskirstant naują atminties vietą metodo skambučio metu. Gali atrodyti keista pasakyti, kad jūsų funkcija tiesiogiai manipuliuos kintamajais, deklaruotu skambučio zonoje, tačiau nepamirškite, kad vykdant sukomponuotą kodą nebėra jokios apimties; yra tik plokščioji atmintis, o jūsų funkcijos kodas gali manipuliuoti bet kokiais kintamaisiais.

Dabar gali būti atvejų, kai kompiliatorius gali nežinoti nuorodos rengiant, pavyzdžiui, kai naudojamas išorinis kintamasis. Taigi, nuoroda gali būti įgyvendinama kaip rodyklė baziniame kode. Tačiau pavyzdžiuose, kuriuos daviau jums, jis greičiausiai nebus įgyvendintas su žymekliu.

63
19 сент. atsakymą pateikė Vincentas Robertas 19 sept. 2008-09-19 15:23 '08 at 15:23 2008-09-19 15:23

Nuorodos yra labai panašios į rodykles, tačiau jos sukurtos tam, kad optimizuotų kompiliatorius.

  • Nuorodos yra suprojektuotos taip, kad kompiliatorius labai supaprastintų atskaitos slapyvardžių, kurie yra kintamieji, sekimą. Labai svarbios dvi svarbios savybės: nėra „atskaitos aritmetikos“ ir nėra nuorodų perskirstymo. Jie leidžia kompiliatoriui išsiaiškinti, kuriose nuorodose yra slapyvardis, kurie kintamieji kompiliavimo metu.
  • Nuorodos gali būti susijusios su kintamaisiais, kuriuose nėra, pavyzdžiui, atminties adresų, kuriuos kompiliatorius nusprendžia įvesti į registrus. Jei paimsite vietinio kintamojo adresą, kompiliatorių labai sunku įrašyti į registrą.

Pavyzdžiui:

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

Optimizavimo kompiliatorius gali suprasti, kad mes pasiekiame [0] ir [1] gana daug. Būtų pageidautina optimizuoti algoritmą taip, kad:

 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 } 

Norint tai padaryti, būtina įrodyti, kad pokalbio metu niekas negali pakeisti masyvo [1]. Tai gana lengva padaryti. Aš esu bent 2, todėl masyvas [i] niekada negali nurodyti masyvo [1]. maybeModify () yra priskirtas a0 kaip nuoroda (aliaso masyvas [0]). Kadangi nėra „atskaitos“ aritmetikos, kompiliatorius paprasčiausiai turi įrodyti, kad tai įmanoma. Modifikacija niekada nesukuria adreso x, ir jis įrodė, kad niekas nepakeičia masyvo [1].

Ji taip pat turi įrodyti, kad nėra jokio būdo, kad būsimas skambutis galėtų skaityti / rašyti [0], o mes turime laikiną registro kopiją a0. Tai dažnai yra nereikšminga įrodyti, nes daugeliu atvejų akivaizdu, kad ši nuoroda niekada nėra saugoma nuolatinėje struktūroje, pvz., Klasės egzemplioriuje.

Dabar atlikite tą patį su rodyklėmis.

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

Elgesys yra toks pat; tik dabar daug sunkiau įrodyti, kad, galbūt, „Modify“ niekada nepakeičia masyvo [1], nes mes jau nurodėme žymiklį; katė išėjo iš maišelio. Dabar jis turi padaryti daug sudėtingesnį įrodymą: statinė analizė gali modifikuoti, kad įrodytų, jog jis niekada neprašo x + 1. Jis taip pat turi įrodyti, kad jis niekada neištrina rodyklės, kuri gali būti susijusi su masyvu [0] kaip sunku.

Šiuolaikiniai kompiliatoriai tampa geresni ir geresni atliekant statinę analizę, tačiau visada malonu juos padėti ir naudoti nuorodas.

Žinoma, išskyrus tokius protingus optimizavimus, kompiliatoriai prireikus nurodo nuorodas į nuorodas.

EDIT: Praėjus penkeriems metams po šio atsakymo paskelbimo, aš rasiu faktinį techninį skirtumą, kai nuorodos skiriasi nuo kito požiūrio į tą pačią problemą. Nuorodos gali pakeisti laikinų objektų veikimo laiką taip, kad rodyklės negalėtų.

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

Paprastai išraiškos pabaigoje paprastai sunaikinami laikini objektai, tokie, kokie yra sukurti skambinant createF(5) . Tačiau, susiejant šį objektą su nuoroda, ref , C ++ pratęs šio laikino objekto tarnavimo laiką, kol ref viršija.

62
01 сент. Atsakymas pateikiamas Cort Ammon 01 sep. 2013-09-01 06:44 '13, 6:44, 2013-09-01 06:44

Nuoroda niekada negali būti NULL .

37
11 сент. Atsakymą pateikė RichS rugsėjo 11 d. 2008-09-11 23:12 '08 at 11:12 pm 2008-09-11 23:12

Nors ir nuorodos, ir nuorodos yra naudojamos netiesiogiai pasiekti kitą vertę, yra du svarbūs skirtumai tarp nuorodų ir nuorodų. Pirma, nuoroda visada nurodo objektą: klaida yra nuorodos apibrėžtyje be jo inicijavimo. Priskyrimo elgesys yra antras svarbus skirtumas: nuorodos priskyrimas pakeičia objektą, prie kurio yra prijungta nuoroda; jis netikrina nuorodos į kitą objektą. Po iniciacijos, nuoroda visada nurodo tą patį pagrindinį objektą.

Apsvarstykite šiuos du programos fragmentus. Pirma, priskiriame vieną žymiklį kitam:

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

Po priskyrimo, ival, objektas, kuriam adresuotas pi, lieka nepakitęs. Priskyrimas pakeičia pi reikšmę, nukreipdamas į kitą objektą. Dabar apsvarstykite panašią programą, kuri priskiria dvi nuorodas:

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

Šis priskyrimas pakeičia ival vertę, kurią nurodo ri, o ne saitą. После назначения две ссылки по-прежнему относятся к их исходным объектам, и значение этих объектов теперь тоже самое.

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