Domanda Quali sono le differenze tra una variabile puntatore e una variabile di riferimento in C ++?


So che i riferimenti sono zucchero sintattico, quindi il codice è più facile da leggere e scrivere.

Ma quali sono le differenze?


Riepilogo delle risposte e dei collegamenti seguenti:

  1. Un puntatore può essere riassegnato qualsiasi numero di volte mentre un riferimento non può essere riassegnato dopo l'associazione.
  2. I puntatori non possono indicare da nessuna parte (NULL), mentre un riferimento si riferisce sempre a un oggetto.
  3. Non puoi prendere l'indirizzo di un riferimento come puoi con i puntatori.
  4. Non c'è "aritmetica di riferimento" (ma puoi prendere l'indirizzo di un oggetto puntato da un riferimento e fare l'aritmetica del puntatore su di esso come in &obj + 5).

Per chiarire un equivoco:

Lo standard C ++ è molto attento a evitare di dettare come può farlo un compilatore   implementare riferimenti, ma ogni compilatore C ++ implementa   riferimenti come puntatori. Cioè, una dichiarazione come:

int &ri = i;

se non è ottimizzato completamente, alloca la stessa quantità di memoria   come puntatore e posiziona l'indirizzo   di i in quella memoria.

Quindi, un puntatore e un riferimento utilizzano entrambi la stessa quantità di memoria.

Come regola generale,

  • Utilizzare i riferimenti nei parametri di funzione e nei tipi restituiti per fornire interfacce utili e autodocumentanti.
  • Utilizzare i puntatori per l'implementazione di algoritmi e strutture dati.

Lettura interessante:


2617
2017-09-11 20:03


origine


risposte:


  1. Un puntatore può essere riassegnato:

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

    Un riferimento non può e deve essere assegnato all'inizializzazione:

    int x = 5;
    int y = 6;
    int &r = x;
    
  2. Un puntatore ha il proprio indirizzo e dimensione di memoria nello stack (4 byte su x86), mentre un riferimento condivide lo stesso indirizzo di memoria (con la variabile originale) ma occupa anche dello spazio nello stack. Poiché un riferimento ha lo stesso indirizzo della stessa variabile originale, è sicuro pensare a un riferimento come un altro nome per la stessa variabile. Nota: ciò a cui punta il puntatore può essere sullo stack o sull'heap. Idem un riferimento. La mia affermazione in questa affermazione non è che un puntatore deve puntare allo stack. Un puntatore è solo una variabile che contiene un indirizzo di memoria. Questa variabile è in pila. Poiché un riferimento ha il proprio spazio nello stack e poiché l'indirizzo è uguale alla variabile a cui fa riferimento. Più su stack vs heap. Ciò implica che esiste un vero indirizzo di riferimento che il compilatore non ti dirà.

    int x = 0;
    int &r = x;
    int *p = &x;
    int *p2 = &r;
    assert(p == p2);
    
  3. Puoi avere dei puntatori ai puntatori ai puntatori che offrono livelli aggiuntivi di riferimento indiretto. Mentre i riferimenti offrono solo un livello di riferimento indiretto.

    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. Il puntatore può essere assegnato nullptr direttamente, mentre il riferimento non può. Se ci provi abbastanza e sai come, puoi creare l'indirizzo di un riferimento nullptr. Allo stesso modo, se ci provi abbastanza puoi avere un riferimento a un puntatore e quindi quel riferimento può contenere 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. I puntatori possono scorrere su un array, è possibile utilizzare ++per andare all'elemento successivo a cui punta il puntatore, e + 4 per andare al quinto elemento. Non importa quale sia l'oggetto a cui punta il puntatore.

  6. Un puntatore deve essere dereferenziato con * per accedere alla posizione di memoria a cui punta, mentre un riferimento può essere utilizzato direttamente. Un puntatore a una classe / struct utilizza -> per accedere ai suoi membri mentre un riferimento usa a ..

  7. Un puntatore è una variabile che contiene un indirizzo di memoria. Indipendentemente da come viene implementato un riferimento, un riferimento ha lo stesso indirizzo di memoria dell'elemento a cui fa riferimento.

  8. I riferimenti non possono essere inseriti in una matrice, mentre i puntatori possono essere (menzionati dall'utente @litb)

  9. I riferimenti di Const possono essere associati a provvisori. I puntatori non possono (non senza qualche indiretta):

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

    Questo fa const& più sicuro da usare negli elenchi di argomenti e così via.


1369
2018-02-27 21:26



Cos'è un riferimento C ++ (per i programmatori C)

UN riferimento può essere pensato come a puntatore costante (da non confondere con un puntatore a un valore costante!) con l'indirezione automatica, cioè il compilatore applicherà il * operatore per te.

Tutti i riferimenti devono essere inizializzati con un valore non nullo o la compilazione avrà esito negativo. Non è possibile ottenere l'indirizzo di un riferimento - l'operatore di indirizzo restituirà invece l'indirizzo del valore di riferimento - né è possibile fare aritmetica sui riferimenti.

I programmatori C potrebbero non gradire i riferimenti C ++ in quanto non saranno più ovvi quando si verifica indiretta o se un argomento viene passato per valore o per puntatore senza guardare le firme delle funzioni.

Ai programmatori C ++ potrebbe non piacere usare i puntatori perché sono considerati non sicuri - sebbene i riferimenti non siano realmente più sicuri dei puntatori costanti tranne nei casi più banali - mancano della convenienza dell'indirizzamento automatico e portano una diversa connotazione semantica.

Si consideri la seguente dichiarazione dal Domande frequenti su C ++:

Anche se un riferimento è spesso implementato utilizzando un indirizzo nel   linguaggio di assemblaggio sottostante, per favore non pensa a un riferimento come a   puntatore dall'aspetto divertente a un oggetto. Un riferimento è l'oggetto. È   non un puntatore all'oggetto, né una copia dell'oggetto. esso è il   oggetto.

Ma se un riferimento veramente erano l'oggetto, come potevano esserci riferimenti ciondolanti? Nei linguaggi non gestiti, è impossibile che i riferimenti siano "più sicuri" dei puntatori: in genere non è un modo per fare in modo affidabile l'alias dei valori attraverso i limiti del campo di applicazione!

Perché considero utili i riferimenti C ++

Provenienti da uno sfondo C, i riferimenti C ++ possono sembrare un concetto un po 'sciocco, ma dovremmo comunque usarli al posto dei puntatori laddove possibile: Indirizzamento automatico è conveniente, e i riferimenti diventano particolarmente utili quando si ha a che fare con RAII - ma non a causa di alcun vantaggio di sicurezza percepito, ma piuttosto perché rendono la scrittura di codice idiomatico meno imbarazzante.

RAII è uno dei concetti centrali del C ++, ma interagisce in modo non banale con la semantica della copia. Passare gli oggetti per riferimento evita questi problemi poiché non è coinvolta alcuna copia. Se i riferimenti non fossero presenti nella lingua, dovresti invece usare dei puntatori, che sono più complicati da usare, violando così il principio del linguaggio design che la soluzione delle migliori pratiche dovrebbe essere più semplice delle alternative.


301
2017-09-11 21:43



Se vuoi essere davvero pedante, c'è una cosa che puoi fare con un riferimento che non puoi fare con un puntatore: estendi la durata di un oggetto temporaneo. In C ++ se si associa un riferimento const a un oggetto temporaneo, la durata di tale oggetto diventa la durata del riferimento.

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

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

In questo esempio, s3_copy copia l'oggetto temporaneo risultato della concatenazione. Mentre s3_reference in sostanza diventa l'oggetto temporaneo. È davvero un riferimento a un oggetto temporaneo che ora ha la stessa durata del riferimento.

Se provi questo senza il const non dovrebbe riuscire a compilare. Non puoi associare un riferimento non const a un oggetto temporaneo, né puoi prendere il suo indirizzo per quella materia.


151
2017-09-11 21:06



Contrariamente all'opinione comune, è possibile avere un riferimento che è NULL.

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

Certo, è molto più difficile da fare con un riferimento - ma se lo gestisci, ti strapperai i capelli cercando di trovarlo. I riferimenti sono non intrinsecamente sicuro in C ++!

Tecnicamente questo è un riferimento non valido, non un riferimento nullo. Il C ++ non supporta i riferimenti null come concetto che potresti trovare in altre lingue. Esistono anche altri tipi di riferimenti non validi. Qualunque riferimento non valido solleva lo spettro di comportamento indefinito, proprio come usare un puntatore non valido.

L'errore effettivo è nel dereferenziamento del puntatore NULL, prima dell'assegnazione a un riferimento. Ma non sono a conoscenza di alcun compilatore che genererà errori su tale condizione - l'errore si propagherà in un punto più avanti nel codice. Questo è ciò che rende questo problema così insidioso. La maggior parte delle volte, se si denota un puntatore NULL, si blocca proprio in quel punto e non ci vuole molto a fare il debug per capirlo.

Il mio esempio sopra è breve e forzato. Ecco un esempio più realistico.

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

Voglio ribadire che l'unico modo per ottenere un riferimento null è attraverso il codice non valido e, una volta ottenuto, si ottiene un comportamento indefinito. esso mai ha senso verificare un riferimento null; per esempio puoi provare if(&bar==NULL)... ma il compilatore potrebbe ottimizzare la dichiarazione fuori dall'esistenza! Un riferimento valido non può mai essere NULL, quindi dal punto di vista del compilatore il confronto è sempre falso ed è libero di eliminare il if clausola come codice morto - questa è l'essenza del comportamento non definito.

Il modo corretto per evitare problemi consiste nell'evitare il dereferenziamento di un puntatore NULL per creare un riferimento. Ecco un modo automatico per realizzare questo.

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

Per uno sguardo più vecchio a questo problema da qualcuno con migliori capacità di scrittura, vedi Riferimenti Null da Jim Hyslop e Herb Sutter.

Per un altro esempio dei pericoli del dereferenziamento si veda un puntatore nullo Esposizione di comportamenti non definiti durante il tentativo di portare il codice su un'altra piattaforma di Raymond Chen.


104
2017-09-11 22:10



Hai dimenticato la parte più importante:

accesso membro con gli usi dei puntatori -> 
accesso membro con riferimenti usa .

foo.bar è chiaramente superiore a foo->bar nello stesso modo in cui VI è chiaramente superiore a Emacs :-)


96
2017-09-11 20:07



Oltre allo zucchero sintattico, un riferimento è a const puntatore (non puntatore a a const). È necessario stabilire a cosa si riferisce quando si dichiara la variabile di riferimento e non è possibile modificarla in seguito.

Aggiornamento: ora che ci penso un po 'di più, c'è una differenza importante.

Il bersaglio di un puntatore const può essere sostituito prendendo il suo indirizzo e usando un cast const.

L'obiettivo di un riferimento non può essere sostituito in alcun modo a corto di UB.

Questo dovrebbe permettere al compilatore di fare più ottimizzazione su un riferimento.


95
2017-09-19 12:23



In realtà, un riferimento non è proprio come un puntatore.

Un compilatore mantiene i "riferimenti" alle variabili, associando un nome ad un indirizzo di memoria; questo è il suo lavoro per tradurre qualsiasi nome di variabile in un indirizzo di memoria durante la compilazione.

Quando crei un riferimento, dici al compilatore solo che assegni un altro nome alla variabile puntatore; ecco perché i riferimenti non possono "puntare a null", perché una variabile non può essere, e non essere.

I puntatori sono variabili; contengono l'indirizzo di qualche altra variabile o possono essere nulli. L'importante è che un puntatore abbia un valore, mentre un riferimento ha solo una variabile a cui fa riferimento.

Ora qualche spiegazione del codice reale:

int a = 0;
int& b = a;

Qui non stai creando un'altra variabile a cui puntare a; stai solo aggiungendo un altro nome al contenuto di memoria che contiene il valore di a. Questa memoria ora ha due nomi, a e be può essere indirizzato usando entrambi i nomi.

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

int a;
increment(a);

Quando si chiama una funzione, il compilatore di solito genera spazi di memoria per gli argomenti da copiare. La firma della funzione definisce gli spazi che dovrebbero essere creati e fornisce il nome che dovrebbe essere usato per questi spazi. La dichiarazione di un parametro come riferimento indica semplicemente al compilatore di utilizzare lo spazio di memoria della variabile di input anziché allocare un nuovo spazio di memoria durante la chiamata al metodo. Può sembrare strano dire che la tua funzione manipolerà direttamente una variabile dichiarata nell'ambito della chiamata, ma ricorda che quando si esegue il codice compilato, non c'è più spazio; c'è solo una memoria piatta e il tuo codice funzione può manipolare qualsiasi variabile.

Ora ci possono essere alcuni casi in cui il compilatore potrebbe non essere in grado di conoscere il riferimento durante la compilazione, come quando si utilizza una variabile esterna. Quindi un riferimento può o non può essere implementato come un puntatore nel codice sottostante. Ma negli esempi che ti ho dato, molto probabilmente non sarà implementato con un puntatore.


57
2017-09-01 03:44