Vraag Wat zijn de verschillen tussen een pointer-variabele en een referentievariabele in C ++?


Ik weet dat referenties syntactische suiker zijn, dus code is gemakkelijker te lezen en te schrijven.

Maar wat zijn de verschillen?


Samenvatting van antwoorden en links hieronder:

  1. Een aanwijzer kan elk aantal keren opnieuw worden toegewezen, terwijl een verwijzing na binding niet opnieuw kan worden toegewezen.
  2. Pointers kunnen nergens wijzen (NULL), terwijl een verwijzing altijd naar een object verwijst.
  3. U kunt het adres van een referentie zoals u kunt niet met pointers nemen.
  4. Er is geen "referentie-rekenkunde" (maar u kunt het adres van een door een referentie aangewezen object nemen en rekenkundige berekeningen erop uitvoeren zoals in &obj + 5).

Om een ​​misvatting op te helderen:

De C ++ -standaard is heel voorzichtig om te voorkomen dat u dicteert hoe een compiler het doet   implementeer referenties, maar elke C ++ compiler implementeert   referenties als verwijzingen. Dat wil zeggen, een verklaring zoals:

int &ri = i;

als het niet helemaal is geoptimaliseerd, wijst dezelfde hoeveelheid opslag toe   als een aanwijzer en plaatst het adres   van i in die opslag.

Een wijzer en een referentie gebruiken dus beide dezelfde hoeveelheid geheugen.

Als een algemene regel,

  • Gebruik referenties in functieparameters en retoursoorten om bruikbare en zelfdocumenterende interfaces te bieden.
  • Gebruik pointers voor het implementeren van algoritmen en datastructuren.

Interessant om te lezen:


2617
2017-09-11 20:03


oorsprong


antwoorden:


  1. Een aanwijzer kan opnieuw worden toegewezen:

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

    Een referentie kan niet, en moet worden toegewezen bij initialisatie:

    int x = 5;
    int y = 6;
    int &r = x;
    
  2. Een aanwijzer heeft zijn eigen geheugenadres en grootte op de stapel (4 bytes op x86), terwijl een referentie hetzelfde geheugenadres deelt (met de oorspronkelijke variabele) maar neemt ook wat ruimte op de stapel in beslag. Omdat een verwijzing hetzelfde adres heeft als de oorspronkelijke variabele zelf, is het veilig om een ​​verwijzing te zien als een andere naam voor dezelfde variabele. Opmerking: waarnaar een aanwijzer verwijst, kan op de stapel of de stapel staan. Dit is een referentie. Mijn claim in deze verklaring is niet dat een aanwijzer naar de stapel moet wijzen. Een aanwijzer is slechts een variabele met een geheugenadres. Deze variabele staat op de stapel. Omdat een verwijzing een eigen spatie op de stapel heeft en omdat het adres hetzelfde is als de variabele waarnaar wordt verwezen. Meer stapel versus hoop. Dit impliceert dat er een echt adres van een referentie is dat de compiler niet zal vertellen.

    int x = 0;
    int &r = x;
    int *p = &x;
    int *p2 = &r;
    assert(p == p2);
    
  3. U kunt verwijzingen naar aanwijzers naar aanwijzers hebben met extra niveaus van indirectie. Terwijl verwijzingen slechts één niveau van indirectie bieden.

    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. Aanwijzer kan worden toegewezen nullptr rechtstreeks, terwijl referentie dat niet kan. Als je hard genoeg probeert, en je weet hoe, kun je het adres van een referentie maken nullptr. Evenzo, als je hard genoeg probeert, kun je een verwijzing naar een aanwijzer hebben en dan kan die verwijzing bevatten 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. Pointers kunnen een matrix herhalen, die u kunt gebruiken ++om naar het volgende item te gaan waarnaar een aanwijzer wijst, en + 4 om naar het 5e element te gaan. Dit is ongeacht de grootte van het object waarnaar de aanwijzer wijst.

  6. Van een aanwijzer moet worden afgeleid * om toegang te krijgen tot de geheugenlocatie waarnaar het verwijst, terwijl een referentie direct kan worden gebruikt. Een verwijzing naar een klasse / struct gebruik -> om toegang te krijgen tot zijn leden terwijl een verwijzing gebruik maakt van een ..

  7. Een aanwijzer is een variabele die een geheugenadres bevat. Ongeacht hoe een referentie wordt geïmplementeerd, een referentie heeft hetzelfde geheugenadres als het item waarnaar wordt verwezen.

  8. Referenties kunnen niet in een array worden gestopt, terwijl pointers kunnen zijn (vermeld door gebruiker @litb)

  9. Const-referenties kunnen aan tijdelijk zijn gebonden. Pointers kunnen dit niet (niet zonder enige indirecte richting):

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

    Dit maakt const& veiliger voor gebruik in argumentlijsten enzovoort.


1369
2018-02-27 21:26



Wat is een C ++ -referentie (voor C-programmeurs)

EEN referentie kan worden gezien als een constante aanwijzer (niet te verwarren met een wijzer naar een constante waarde!) met automatische indirectie, dwz de compiler past de * operator voor u.

Alle verwijzingen moeten worden geïnitialiseerd met een niet-nulwaarde of de compilatie mislukt. Het is niet mogelijk om het adres van een referentie te krijgen - de adresoperator zal in plaats daarvan het adres van de vermelde waarde retourneren - en het is ook niet mogelijk om rekenkundige bewerkingen op verwijzingen uit te voeren.

C-programmeurs hebben mogelijk een hekel aan C ++ -referenties omdat het niet langer duidelijk zal zijn wanneer indirectie optreedt of wanneer een argument wordt gepasseerd door een waarde of door een aanwijzer zonder naar functionele handtekeningen te kijken.

C ++ programmeurs hebben misschien een hekel aan het gebruik van aanwijzers omdat ze als onveilig worden beschouwd - hoewel verwijzingen niet echt veiliger zijn dan constante aanwijzers behalve in de meest triviale gevallen - ontbreken het gemak van automatische indirectie en hebben ze een andere semantische connotatie.

Beschouw de volgende verklaring van de Veelgestelde vragen over C ++:

Hoewel een verwijzing vaak wordt geïmplementeerd met behulp van een adres in de   onderliggende assembleertaal, alsjeblieft niet denk aan een referentie als een   grappig uitziende wijzer naar een object. Een referentie is het object. Het is   geen aanwijzer naar het object, noch een kopie van het object. Het is de   voorwerp.

Maar als een referentie werkelijk waren het object, hoe konden er bengelende verwijzingen zijn? In niet-beheerde talen is het onmogelijk dat verwijzingen 'veiliger' zijn dan verwijzingen - over het algemeen is het gewoon geen manier om aliassen betrouwbaar te overschrijven over de grenzen van het bereik heen!

Waarom ik C ++ -referenties nuttig vind

Komende van een C-achtergrond, kunnen C ++ -referenties eruit zien als een enigszins dwaas concept, maar je moet ze waar mogelijk nog steeds gebruiken in plaats van verwijzingen: Automatische indirectie is handig, en verwijzingen worden vooral handig bij het omgaan met RAII - maar niet vanwege enig waargenomen veiligheidsvoordeel, maar eerder omdat ze het schrijven van idiomatische code minder ongemakkelijk maken.

RAII is een van de centrale concepten van C ++, maar het werkt niet-triviaal samen met de semantiek van het kopiëren. Objecten door verwijzing doorgeven vermijdt deze problemen omdat er geen sprake is van kopiëren. Als er geen referenties in de taal aanwezig waren, zou je in plaats daarvan pointers moeten gebruiken, die omslachtiger te gebruiken zijn, en dus het taalontwerpprincipe schenden dat de best-practice oplossing eenvoudiger zou moeten zijn dan de alternatieven.


301
2017-09-11 21:43



Als je heel pedant wilt zijn, is er één ding dat je kunt doen met een referentie die je niet met een aanwijzer kunt doen: verleng de levensduur van een tijdelijk object. In C ++ als u een const-verwijzing aan een tijdelijk object koppelt, wordt de levensduur van dat object de levensduur van de referentie.

std::string s1 = "123";
std::string s2 = "456";

std::string s3_copy = s1 + s2;
const std::string& s3_reference = s1 + s2;

In dit voorbeeld kopieert s3_copy het tijdelijke object dat het resultaat is van de aaneenschakeling. Terwijl s3_reference in essentie het tijdelijke doel wordt. Het is echt een verwijzing naar een tijdelijk object dat nu dezelfde levensduur heeft als de referentie.

Als je dit probeert zonder de const het zou niet moeten compileren. U kunt een niet-const-verwijzing naar een tijdelijk object niet binden, en u kunt ook niet zijn adres nemen.


151
2017-09-11 21:06



In tegenstelling tot wat vaak wordt gedacht, is het mogelijk om een ​​referentie te hebben die NULL is.

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

Toegegeven, het is veel moeilijker om te doen met een referentie - maar als het je lukt, scheur je je haar uit en probeer je het te vinden. Referenties zijn niet inherent veilig in C ++!

Technisch gezien is dit een ongeldige referentie, geen nulreferentie. C ++ ondersteunt geen nulreferenties als een concept dat u wellicht in andere talen tegenkomt. Er zijn ook andere soorten ongeldige verwijzingen. Ieder ongeldige verwijzing verhoogt het spook van ongedefinieerd gedrag, net zoals het gebruik van een ongeldige pointer zou doen.

De werkelijke fout zit in de dereferentie van de NULL-pointer, voorafgaand aan de toewijzing aan een referentie. Maar ik ben niet op de hoogte van eventuele compilers die fouten op die voorwaarde zullen genereren - de fout verspreidt zich naar een punt verderop in de code. Dat is wat dit probleem zo verraderlijk maakt. Meestal, als je een NULL pointer dereference, crash je precies op die plek en het kost niet veel debugging om het uit te zoeken.

Mijn voorbeeld hierboven is kort en gekunsteld. Hier is een meer realistisch voorbeeld.

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

Ik wil herhalen dat de enige manier om een ​​nulreferentie te krijgen, is door verkeerd ingedeelde code, en als je het eenmaal hebt, krijg je ongedefinieerd gedrag. Het nooit is logisch om te controleren op een nulreferentie; je kunt het bijvoorbeeld proberen if(&bar==NULL)... maar de compiler zou de verklaring kunnen optimaliseren! Een geldige verwijzing kan nooit NULL zijn, dus vanuit de mening van de compiler is de vergelijking altijd fout, en is het vrij om de vergelijking te elimineren if clausule als dode code - dit is de essentie van ongedefinieerd gedrag.

De juiste manier om uit de problemen te blijven, is om te voorkomen dat u een NULL-pointer volgt om een ​​referentie te maken. Hier is een geautomatiseerde manier om dit te bereiken.

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

Kijk voor een oudere blik op dit probleem van iemand met een betere schrijfvaardigheid Null-verwijzingen van Jim Hyslop en Herb Sutter.

Voor een ander voorbeeld van de gevaren van dereferencing ziet een null pointer Ongedefinieerd gedrag blootleggen wanneer u probeert code naar een ander platform te verzenden door Raymond Chen.


104
2017-09-11 22:10



Je bent het belangrijkste deel vergeten:

lid-toegang met pointers-gebruik -> 
lid-toegang met gebruik van referenties .

foo.bar is duidelijk superieur aan foo->bar op dezelfde manier dat vi is duidelijk superieur aan Emacs :-)


96
2017-09-11 20:07



Afgezien van syntactische suiker, is een verwijzing een const pointer (niet pointer naar a const). U moet vaststellen waarnaar het verwijst wanneer u de referentievariabele declareert en u kunt dit later niet wijzigen.

Update: nu ik er meer over nadenk, is er een belangrijk verschil.

Het doelwit van een const-aanwijzer kan worden vervangen door het adres ervan te nemen en een const-cast te gebruiken.

Het doel van een verwijzing kan op geen enkele manier worden vervangen door UB.

Dit zou de compiler in staat moeten stellen meer te optimaliseren voor een referentie.


95
2017-09-19 12:23



Eigenlijk is een referentie niet echt een aanwijzer.

Een compiler houdt "verwijzingen" naar variabelen, associeert een naam met een geheugenadres; dat is zijn taak om elke variabelenaam naar een geheugenadres te vertalen tijdens het compileren.

Wanneer u een referentie maakt, vertelt u de compiler dat u een andere naam toewijst aan de pointervariabele; dat is de reden waarom verwijzingen niet naar "nul" kunnen verwijzen, omdat een variabele dat niet kan zijn en niet kan zijn.

Pointers zijn variabelen; ze bevatten het adres van een andere variabele of kunnen nul zijn. Het belangrijkste is dat een aanwijzer een waarde heeft, terwijl een verwijzing alleen een variabele heeft waarnaar wordt verwezen.

Nu wat uitleg over echte code:

int a = 0;
int& b = a;

Hier creëer je geen andere variabele waarnaar wordt verwezen a; u voegt gewoon een andere naam toe aan de geheugeninhoud die de waarde van bevat a. Dit geheugen heeft nu twee namen, a en ben het kan worden geadresseerd met behulp van een van beide namen.

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

int a;
increment(a);

Wanneer u een functie aanroept, genereert de compiler gewoonlijk geheugenruimten voor de argumenten waarnaar moet worden gekopieerd. De functiesignatuur definieert de spaties die moeten worden gemaakt en geeft de naam die moet worden gebruikt voor deze spaties. Het declareren van een parameter als referentie vertelt de compiler gewoon om de variabele geheugenruimte van de ingang te gebruiken in plaats van een nieuwe geheugenruimte toe te kennen tijdens de methodeaanroep. Het lijkt misschien vreemd om te zeggen dat je functie direct een variabele gedeclareerd in het oproepbereik zal manipuleren, maar vergeet niet dat bij het uitvoeren van gecompileerde code er geen ruimte meer is; er is gewoon een plat geheugen en uw functiecode kan variabelen manipuleren.

Nu kunnen er enkele gevallen zijn waarbij uw compiler mogelijk niet de referentie kan kennen bij het compileren, zoals bij het gebruik van een externe variabele. Dus een verwijzing kan al dan niet worden geïmplementeerd als een aanwijzer in de onderliggende code. Maar in de voorbeelden die ik je heb gegeven, zal het waarschijnlijk niet worden geïmplementeerd met een aanwijzer.


57
2017-09-01 03:44



Verwijzingen lijken erg op aanwijzers, maar ze zijn specifiek ontworpen om te helpen bij het optimaliseren van compilers.

  • Referenties zijn zo ontworpen dat het voor de compiler aanzienlijk eenvoudiger is om te achterhalen welke referentiealiassen welke variabelen zijn. Twee belangrijke kenmerken zijn erg belangrijk: geen "referentierekeningen" en geen toewijzing van referenties. Hiermee kan de compiler achterhalen welke referenties al die variabelen tijdens het compileren aanduiden.
  • Verwijzingen kunnen verwijzen naar variabelen die geen geheugenadressen hebben, zoals die de compiler kiest om in registers te plaatsen. Als u het adres van een lokale variabele opneemt, is het erg moeilijk voor de compiler om het in een register te plaatsen.

Als voorbeeld:

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

Een optimaliserende compiler kan zich realiseren dat we een [0] en een [1] een heel stel gebruiken. Het zou graag het algoritme optimaliseren om:

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
}

Om een ​​dergelijke optimalisatie te maken, moet het bewijzen dat niets array [1] kan veranderen tijdens het gesprek. Dit is vrij eenvoudig om te doen. i is nooit minder dan 2, dus array [i] kan nooit verwijzen naar array [1]. mightModify () krijgt a0 als een referentie (aliasing array [0]). Omdat er geen "referentie" -rekenkunde is, hoeft de compiler alleen maar te bewijzen dat misschien Modify nooit het adres van x krijgt, en het heeft bewezen dat niets array verandert [1].

Het moet ook bewijzen dat er geen manieren zijn waarop een toekomstige oproep een [0] kan lezen / schrijven, terwijl we er een tijdelijk register van hebben in a0. Dit is vaak triviaal om te bewijzen, omdat in veel gevallen het duidelijk is dat de verwijzing nooit wordt opgeslagen in een permanente structuur zoals een klasseninstantie.

Doe nu hetzelfde met aanwijzingen

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

Het gedrag is hetzelfde; alleen nu is het veel moeilijker om aan te tonen dat modifiedModify array [1] nooit zal wijzigen, omdat we het al een aanwijzer hebben gegeven; de kat is uit de zak. Nu moet het het veel moeilijkere bewijs doen: een statische analyse van misschien Modify om te bewijzen dat het nooit naar & x + 1 schrijft. Het moet ook bewijzen dat het nooit een aanwijzer redt die kan verwijzen naar array [0], wat gewoon zo lastig.

Moderne compilers worden steeds beter en beter bij statische analyse, maar het is altijd leuk om ze te helpen en referenties te gebruiken.

Natuurlijk behouden compilers, behoudens zulke slimme optimalisaties, inderdaad verwijzingen naar verwijzingen wanneer dat nodig is.


50
2017-09-11 20:12