pointers punteros - ¿Cuáles son las diferencias entre una variable de puntero y una variable de referencia en C++?



java pdf (25)

Sé que las referencias son azúcar sintáctica, por lo que el código es más fácil de leer y escribir.

Pero ¿cuáles son las diferencias?

Resumen de las respuestas y enlaces a continuación:

  1. Un puntero se puede reasignar cualquier cantidad de veces, mientras que una referencia no se puede reasignar después del enlace.
  2. Los punteros no pueden apuntar a ninguna parte ( NULL ), mientras que una referencia siempre se refiere a un objeto.
  3. No puede tomar la dirección de una referencia como puede con punteros.
  4. No hay "aritmética de referencia" (pero puede tomar la dirección de un objeto señalado por una referencia y hacer aritmética de punteros en él como en &obj + 5 ).

Para aclarar una idea falsa:

El estándar de C ++ es muy cuidadoso para evitar dictar cómo un compilador puede implementar referencias, pero cada compilador de C ++ implementa referencias como punteros. Es decir, una declaración como:

int &ri = i;

si no se optimiza por completo , asigna la misma cantidad de almacenamiento que un puntero y coloca la dirección de i en ese almacenamiento.

Entonces, tanto el puntero como la referencia usan la misma cantidad de memoria.

Como regla general,

  • Utilice referencias en los parámetros de función y tipos de retorno para proporcionar interfaces útiles y autodocumentadas.
  • Utilice punteros para implementar algoritmos y estructuras de datos.

LecturA INTERESANTE:


Answers

Otro uso interesante de las referencias es proporcionar un argumento predeterminado de un tipo definido por el usuario:

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

El sabor predeterminado utiliza el aspecto 'vincular const de un temporal' de las referencias.


No importa la cantidad de espacio que ocupe, ya que realmente no puede ver ningún efecto secundario (sin ejecutar código) del espacio que ocuparía.

Por otro lado, una diferencia importante entre las referencias y los punteros es que los temporales asignados a las referencias const se mantienen en vivo hasta que la referencia de const se sale del alcance.

Por ejemplo:

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

...

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

imprimirá:

in scope
scope_test done!

Este es el mecanismo del lenguaje que permite que ScopeGuard funcione.


Uso referencias a menos que necesite alguno de estos:

  • Los punteros nulos pueden usarse como un valor centinela, a menudo una forma barata de evitar la sobrecarga de funciones o el uso de un bool.

  • Puedes hacer aritmética en un puntero. Por ejemplo,p += offset;


Diferencia entre puntero y referencia.

Un puntero se puede inicializar a 0 y una referencia no. De hecho, una referencia también debe referirse a un objeto, pero un puntero puede ser el puntero nulo:

int* p = 0;

Pero no podemos tener int& p = 0;y tambiénint& p=5 ; .

De hecho, para hacerlo correctamente, debemos haber declarado y definido un objeto al principio, luego podemos hacer una referencia a ese objeto, por lo que la implementación correcta del código anterior será:

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

Otro punto importante es que podemos hacer la declaración del puntero sin inicialización, sin embargo, no se puede hacer tal cosa en el caso de una referencia que debe hacer referencia siempre a la variable o al objeto. Sin embargo, tal uso de un puntero es arriesgado, por lo que generalmente verificamos si el puntero en realidad apunta a algo o no. En el caso de una referencia, no es necesaria tal verificación, porque ya sabemos que la referencia a un objeto durante la declaración es obligatoria.

Otra diferencia es que el puntero puede apuntar a otro objeto; sin embargo, la referencia siempre hace referencia al mismo objeto, tomemos este ejemplo:

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

Otro punto: cuando tenemos una plantilla como una plantilla STL, un tipo de plantilla de clase siempre devolverá una referencia, no un puntero, para facilitar la lectura o asignar un nuevo valor utilizando el operador []:

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 "="

Siempre decido por this regla de C ++ Core Guidelines:

Prefiero T * sobre T y cuando "sin argumento" es una opción válida


Siento que hay otro punto que no ha sido cubierto aquí.

A diferencia de los punteros, las referencias son sintácticamente equivalentes al objeto al que hacen referencia, es decir, cualquier operación que se pueda aplicar a un objeto funciona para una referencia, y con la misma sintaxis (la excepción es la inicialización).

Si bien esto puede parecer superficial, creo que esta propiedad es crucial para varias funciones de C ++, por ejemplo:

  • Plantillas . Como los parámetros de la plantilla son tipográficos, las propiedades sintácticas de un tipo es lo único que importa, por lo que a menudo se puede usar la misma plantilla con Ty T&.
    (o std::reference_wrapper<T>que aún se basa en una conversión implícita a T&)
    Plantillas que cubren ambas T&y T&&son aún más comunes.

  • Lvalues . Considere la declaración str[0] = 'X';Sin referencias, solo funcionaría para c-strings ( char* str). Devolver el carácter por referencia permite que las clases definidas por el usuario tengan la misma notación.

  • Copiar constructores . Sintácticamente tiene sentido pasar objetos para copiar constructores, y no punteros a objetos. Pero simplemente no hay forma de que un constructor de copia tome un objeto por valor, lo que daría lugar a una llamada recursiva al mismo constructor de copia. Esto deja las referencias como la única opción aquí.

  • Sobrecargas del operador . Con las referencias es posible introducir el direccionamiento indirecto a una llamada de operador, por ejemplo, operator+(const T& a, const T& b)manteniendo la misma notación de infijo. Esto también funciona para funciones sobrecargadas regulares.

Estos puntos dan poder a una parte considerable de C ++ y la biblioteca estándar, por lo que esta es una propiedad importante de las referencias.


¿Qué es una referencia de C ++ ( para programadores de C )

Una referencia se puede considerar como un puntero constante (¡no debe confundirse con un puntero a un valor constante!) Con direccionamiento automático automático, es decir, el compilador aplicará el operador * para usted.

Todas las referencias deben inicializarse con un valor que no sea nulo o la compilación fallará. No es posible obtener la dirección de una referencia (el operador de la dirección devolverá la dirección del valor de referencia en su lugar), ni tampoco es posible hacer aritmética en las referencias.

A los programadores de C les pueden disgustar las referencias de C ++ ya que ya no serán obvias cuando ocurra la indirección o si un argumento se pasa por valor o por puntero sin mirar las firmas de las funciones.

A los programadores de C ++ les puede disgustar el uso de punteros, ya que se consideran inseguros, aunque las referencias no son más seguras que los punteros constantes, excepto en los casos más triviales, carecen de la conveniencia de la indirección automática y tienen una connotación semántica diferente.

Considere la siguiente declaración de las preguntas frecuentes de C ++ :

Aunque a menudo se implementa una referencia utilizando una dirección en el lenguaje ensamblador subyacente, no piense en una referencia como un puntero de aspecto divertido a un objeto. Una referencia es el objeto. No es un puntero al objeto, ni una copia del objeto. Es el objeto.

Pero si una referencia realmente fuera el objeto, ¿cómo podría haber referencias colgantes? En lenguajes no administrados, es imposible que las referencias sean "más seguras" que los punteros. En general, simplemente no hay una forma de alias confiable de valores a través de los límites del alcance.

Por qué considero útiles las referencias a C ++

Al provenir de un fondo en C, las referencias a C ++ pueden parecer un concepto un tanto tonto, pero aún se deben usar en lugar de indicadores cuando sea posible: la indirección automática es conveniente, y las referencias son especialmente útiles cuando se trata de RAII , pero no debido a la seguridad percibida ventaja, sino más bien porque hacen que escribir código idiomático sea menos incómodo.

RAII es uno de los conceptos centrales de C ++, pero interactúa de manera no trivial con la copia de la semántica. Pasar objetos por referencia evita estos problemas ya que no se trata de copiar. Si las referencias no estuvieran presentes en el idioma, tendría que usar punteros, que son más incómodos de usar, violando así el principio de diseño del lenguaje de que la mejor solución debería ser más fácil que las alternativas.


Una referencia no es otro nombre dado a alguna memoria. Es un puntero inmutable que se elimina automáticamente en el uso. Básicamente se reduce a:

int& j = i;

Se convierte internamente

int* const j = &i;

Una referencia nunca puede ser NULL .


Tal vez algunas metáforas ayuden; En el contexto de su pantalla de escritorio -

  • Una referencia requiere que especifique una ventana real.
  • Un puntero requiere la ubicación de un espacio en la pantalla para asegurar que contendrá cero o más instancias de ese tipo de ventana.

Aparte del azúcar sintáctico, una referencia es un puntero de const ( no un puntero a una const ). Debe establecer a qué se refiere cuando declara la variable de referencia y no puede cambiarla más adelante.

Actualización: ahora que lo pienso un poco más, hay una diferencia importante.

El objetivo de un puntero de const se puede reemplazar tomando su dirección y usando una conversión constante.

El objetivo de una referencia no puede ser reemplazado de ninguna manera por debajo de UB.

Esto debería permitir al compilador hacer más optimización en una referencia.


Existe una diferencia semántica que puede parecer esotérica si no está familiarizado con el estudio de los lenguajes informáticos de forma abstracta o incluso académica.

En el nivel más alto, la idea de las referencias es que son transparentes "alias". Su computadora puede usar una dirección para hacer que funcionen, pero se supone que no debe preocuparse por eso: se supone que debe pensar en ellos como "solo otro nombre" para un objeto existente y la sintaxis lo refleja. Son más estrictos que los punteros, por lo que su compilador puede advertirle de manera más confiable cuando está a punto de crear una referencia colgante, que cuando está a punto de crear un puntero colgante.

Más allá de eso, hay, por supuesto, algunas diferencias prácticas entre los punteros y las referencias. La sintaxis para usarlos es obviamente diferente, y no puede "volver a colocar" las referencias, tener referencias a la nada, o apuntar a referencias.


Otra diferencia es que puede tener punteros a un tipo de vacío (y significa puntero a cualquier cosa) pero las referencias a vacío están prohibidas.

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

No puedo decir que estoy realmente feliz con esta diferencia particular. Preferiría que se permitiera con el significado de referencia a cualquier cosa con una dirección y, por lo demás, el mismo comportamiento para las referencias. Permitiría definir algunos equivalentes de las funciones de la biblioteca C como memcpy usando referencias.


Esto se basa en el tutorial . Lo que está escrito lo hace más claro:

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

Simplemente para recordar eso,

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

Además, como podemos referirnos a casi cualquier tutorial de punteros, un puntero es un objeto que es compatible con la aritmética de punteros que hace que el puntero sea similar a una matriz.

Mira la siguiente declaración,

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

alias_TomPuede entenderse como un alias of a variable(diferente con typedef, que es alias of a type) Tom. También está bien olvidar que la terminología de dicha declaración es crear una referencia de Tom.


Una referencia es un alias para otra variable, mientras que un puntero contiene la dirección de memoria de una variable. Las referencias se utilizan generalmente como parámetros de función para que el objeto pasado no sea la copia sino el objeto en sí.

    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. 

Si bien tanto las referencias como los punteros se utilizan para acceder indirectamente a otro valor, existen dos diferencias importantes entre las referencias y los punteros. La primera es que una referencia siempre se refiere a un objeto: es un error definir una referencia sin inicializarlo. El comportamiento de la asignación es la segunda diferencia importante: la asignación a una referencia cambia el objeto al que está vinculada la referencia; no vuelve a enlazar la referencia a otro objeto. Una vez inicializada, una referencia siempre se refiere al mismo objeto subyacente.

Considere estos dos fragmentos del programa. En la primera, asignamos un puntero a otro:

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

Después de la asignación, ival, el objeto direccionado por pi permanece sin cambios. La asignación cambia el valor de pi, por lo que apunta a un objeto diferente. Ahora considere un programa similar que asigna dos referencias:

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

Esta asignación cambia ival, el valor referenciado por ri, y no la referencia en sí. Después de la asignación, las dos referencias aún se refieren a sus objetos originales, y el valor de esos objetos ahora también es el mismo.


En realidad, una referencia no es realmente como un puntero.

Un compilador mantiene "referencias" a las variables, asociando un nombre con una dirección de memoria; ese es su trabajo para traducir cualquier nombre de variable a una dirección de memoria al compilar.

Cuando creas una referencia, solo le dices al compilador que asignas otro nombre a la variable de puntero; es por eso que las referencias no pueden "apuntar a nulo", porque una variable no puede ser, y no puede ser.

Los punteros son variables; contienen la dirección de alguna otra variable, o pueden ser nulas. Lo importante es que un puntero tiene un valor, mientras que una referencia solo tiene una variable a la que hace referencia.

Ahora una explicación del código real:

int a = 0;
int& b = a;

Aquí no estás creando otra variable que apunta a a ; solo está agregando otro nombre al contenido de la memoria que tiene el valor de a . Esta memoria ahora tiene dos nombres, a y b , y puede direccionarse usando cualquiera de los dos nombres.

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

int a;
increment(a);

Al llamar a una función, el compilador generalmente genera espacios de memoria para los argumentos a copiar. La firma de función define los espacios que se deben crear y le da el nombre que se debe usar para estos espacios. Declarar un parámetro como referencia simplemente le dice al compilador que use el espacio de memoria de la variable de entrada en lugar de asignar un nuevo espacio de memoria durante la llamada al método. Puede parecer extraño decir que su función manipulará directamente una variable declarada en el alcance de la llamada, pero recuerde que al ejecutar el código compilado, no hay más alcance; solo hay una memoria plana y su código de función podría manipular cualquier variable.

Ahora puede haber algunos casos en los que su compilador no pueda conocer la referencia al compilar, como cuando usa una variable externa. Por lo tanto, una referencia puede o no implementarse como un puntero en el código subyacente. Pero en los ejemplos que te di, lo más probable es que no se implemente con un puntero.


Existe una diferencia no técnica muy importante entre los punteros y las referencias: un argumento que se pasa a una función mediante un puntero es mucho más visible que un argumento que se pasa a una función por una referencia no constante. Por ejemplo:

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

De vuelta en C, una llamada que parece fn(x)solo puede pasarse por valor, por lo que definitivamente no se puede modificar x; para modificar un argumento necesitarías pasar un puntero fn(&x). Entonces, si un argumento no estuviera precedido por un &sabías, no se modificaría. (Lo contrario, &significa que se modificó, no era cierto porque a veces tendría que pasar grandes estructuras de solo lectura con el constpuntero).

Algunos argumentan que esta es una característica tan útil al leer el código, que los parámetros de puntero siempre deben usarse para parámetros modificables en lugar de no constreferencias, incluso si la función nunca espera a nullptr. Es decir, esas personas argumentan que fn3()no deben permitirse las firmas de funciones como la anterior. Las pautas de estilo de C ++ de Google son un ejemplo de esto.


Si desea ser realmente pedante, hay una cosa que puede hacer con una referencia que no puede hacer con un puntero: extender la vida útil de un objeto temporal. En C ++, si vincula una referencia constante a un objeto temporal, el tiempo de vida de ese objeto se convierte en el tiempo de vida de la referencia.

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

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

En este ejemplo, s3_copy copia el objeto temporal que es el resultado de la concatenación. Mientras que s3_reference en esencia se convierte en el objeto temporal. Realmente es una referencia a un objeto temporal que ahora tiene la misma duración que la referencia.

Si intentas esto sin la const , debería fallar la compilación. No puede enlazar una referencia no constante a un objeto temporal, ni tampoco puede tomar su dirección.


A riesgo de agregar confusión, quiero agregar algo de información, estoy seguro de que depende principalmente de cómo el compilador implementa las referencias, pero en el caso de gcc, la idea de que una referencia solo puede apuntar a una variable en la pila. no es realmente correcto, toma esto por ejemplo:

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

Que produce esto:

THIS IS A STRING
0xbb2070 : 0xbb2070

¡Si observa que incluso las direcciones de memoria son exactamente iguales, significa que la referencia apunta con éxito a una variable en el montón! Ahora si realmente quieres ponerte raro, esto también funciona:

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).*/
}

Que produce esto:

THIS IS A STRING

Por lo tanto, una referencia ES un puntero debajo del capó, ambos están simplemente almacenando una dirección de memoria, donde la dirección que apunta es irrelevante, ¿qué crees que pasaría si llamara a std :: cout << str_ref; DESPUÉS de llamar a delete & str_ref? Bueno, obviamente se compila bien, pero causa una falla de segmentación en tiempo de ejecución porque ya no apunta a una variable válida, esencialmente tenemos una referencia rota que aún existe (hasta que queda fuera de alcance), pero es inútil.

En otras palabras, una referencia no es más que un puntero que tiene abstraída la mecánica del puntero, lo que lo hace más seguro y más fácil de usar (sin matemáticas de punteros accidentales, sin mezclar '.' Y '->', etc.), suponiendo no intentes tonterías como mis ejemplos anteriores;)

Ahora, independientemente de cómo un compilador maneje las referencias, siempre tendrá algún tipo de puntero debajo del capó, porque una referencia debe referirse a una variable específica en una dirección de memoria específica para que funcione como se espera, no hay forma de evitar esto (por lo tanto, el término 'referencia').

La única regla importante que es importante recordar con referencias es que deben definirse en el momento de la declaración (con la excepción de una referencia en un encabezado, en ese caso, debe definirse en el constructor, una vez que el objeto en el que está contenido es construido es demasiado tarde para definirlo).

Recuerde, mis ejemplos anteriores son solo eso, ejemplos que demuestran qué es una referencia, ¡nunca querría usar una referencia de esa manera! Para el uso adecuado de una referencia, ya hay muchas respuestas aquí que golpean el clavo en la cabeza.


Hay una diferencia fundamental entre los punteros y las referencias que no vi que nadie haya mencionado: las referencias permiten la semántica paso por referencia en los argumentos de función. Los punteros, aunque no son visibles al principio, no lo hacen: solo proporcionan semántica de paso por valor. Esto ha sido muy bien descrito en este artículo .

Saludos, y rzej


Tanto las referencias como los punteros se pueden usar para cambiar las variables locales de una función dentro de otra función. Ambos también se pueden usar para guardar la copia de objetos grandes cuando se pasan como argumentos a funciones o se devuelven desde funciones, para obtener mayor eficiencia. A pesar de las similitudes anteriores, hay diferencias siguientes entre referencias y punteros.

Las referencias son menos poderosas que los punteros.

1) Una vez que se crea una referencia, no se puede hacer posteriormente para hacer referencia a otro objeto; no se puede volver a colocar Esto se hace a menudo con punteros.

2) Las referencias no pueden ser NULL. Los punteros a menudo se hacen NULOS para indicar que no están apuntando a ninguna cosa válida.

3) Una referencia debe ser inicializada cuando se declara. No hay tal restricción con punteros

Debido a las limitaciones anteriores, las referencias en C ++ no se pueden usar para implementar estructuras de datos como Lista enlazada, Árbol, etc. En Java, las referencias no tienen las restricciones anteriores y se pueden usar para implementar todas las estructuras de datos. Las referencias son más poderosas en Java, es la razón principal por la que Java no necesita punteros.

Las referencias son más seguras y fáciles de usar:

1) Más seguro: ya que las referencias deben inicializarse, es poco probable que existan referencias salvajes como punteros de comodín. Todavía es posible tener referencias que no se refieren a una ubicación válida

2) Más fácil de usar: las referencias no necesitan un operador de referencia para acceder al valor. Se pueden utilizar como variables normales. El operador '&' solo es necesario en el momento de la declaración. Además, se puede acceder a los miembros de una referencia de objeto con el operador de punto ('.'), A diferencia de los punteros donde se necesita el operador de flecha (->) para acceder a los miembros.

Junto con las razones anteriores, hay algunos lugares como el argumento del constructor de copia donde no se puede usar el puntero. Referencia debe ser utilizada pasar el argumento en el constructor de copia. Se deben usar referencias similares para sobrecargar algunos operadores como ++ .


Además, una referencia que es un parámetro de una función que está en línea puede manejarse de manera diferente a un puntero.

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

Muchos compiladores al insertar la versión de puntero uno forzarán realmente una escritura en la memoria (estamos tomando la dirección explícitamente). Sin embargo, dejarán la referencia en un registro que sea más óptimo.

Por supuesto, para las funciones que no están en línea, el puntero y la referencia generan el mismo código y siempre es mejor pasar valores intrínsecos por valor que por referencia si no son modificados y devueltos por la función.


  1. Un puntero puede ser reasignado:

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

    Una referencia no puede y debe asignarse en la inicialización:

    int x = 5;
    int y = 6;
    int &r = x;
    
  2. Un puntero tiene su propia dirección de memoria y tamaño en la pila (4 bytes en x86), mientras que una referencia comparte la misma dirección de memoria (con la variable original) pero también ocupa algo de espacio en la pila. Dado que una referencia tiene la misma dirección que la variable original, es seguro pensar en una referencia como otro nombre para la misma variable. Nota: lo que apunta un puntero puede estar en la pila o el montón. Ditto una referencia. Mi afirmación en esta declaración no es que un puntero debe apuntar a la pila. Un puntero es solo una variable que contiene una dirección de memoria. Esta variable está en la pila. Dado que una referencia tiene su propio espacio en la pila, y como la dirección es la misma que la variable a la que hace referencia. Más en la pila vs montón . Esto implica que hay una dirección real de una referencia que el compilador no le dirá.

    int x = 0;
    int &r = x;
    int *p = &x;
    int *p2 = &r;
    assert(p == p2);
    
  3. Puede tener punteros a punteros a punteros que ofrecen niveles adicionales de direccionamiento indirecto. Mientras que las referencias solo ofrecen un nivel de direccionamiento indirecto.

    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. El puntero puede asignarse directamente a nullptr , mientras que la referencia no puede. Si se esfuerza lo suficiente, y sabe cómo, puede hacer que la dirección de una referencia nullptr . Del mismo modo, si se esfuerza lo suficiente, puede tener una referencia a un puntero, y esa referencia puede contener 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. Los punteros pueden iterar sobre una matriz, puede usar ++ para ir al siguiente elemento al que apunta un puntero y + 4 para ir al quinto elemento. Esto no importa el tamaño del objeto al que apunta el puntero.

  6. Un puntero debe ser referenciado con * para acceder a la ubicación de memoria a la que apunta, mientras que una referencia se puede usar directamente. Un puntero a una clase / estructura usa -> para acceder a sus miembros, mientras que una referencia usa a . .

  7. Un puntero es una variable que contiene una dirección de memoria. Independientemente de cómo se implementa una referencia, una referencia tiene la misma dirección de memoria que el elemento al que hace referencia.

  8. Las referencias no se pueden rellenar en una matriz, mientras que los punteros pueden ser (mencionados por el usuario @litb)

  9. Las referencias const pueden estar vinculadas a los temporales. Los punteros no pueden (no sin alguna indirección):

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

    Esto hace que const& más seguro para su uso en listas de argumentos y así sucesivamente.


Este es uno de los mensajes de error más confusos que todos los programadores de VC ++ han visto una y otra vez. Vamos a hacer las cosas primero la claridad.

A. ¿Qué es el símbolo? En resumen, un símbolo es un nombre. Puede ser un nombre de variable, un nombre de función, un nombre de clase, un nombre typedef o cualquier cosa, excepto los nombres y signos que pertenecen al lenguaje C ++. Es definido por el usuario o introducido por una biblioteca de dependencia (otra definida por el usuario).

B. ¿Qué es externo? En VC ++, cada archivo de origen (.cpp, .c, etc.) Se considera como una unidad de traducción, el compilador compila una unidad a la vez y genera un archivo de objeto (.obj) para la unidad de traducción actual. (Tenga en cuenta que cada archivo de encabezado que se incluye en este archivo de origen se procesará previamente y se considerará como parte de esta unidad de traducción) Todo lo que se encuentra dentro de una unidad de traducción se considera interno, todo lo demás se considera externo. En C ++, puede hacer referencia a un símbolo externo usando palabras clave como extern , __declspec (dllimport) y así sucesivamente.

C. ¿Qué es “resolver”? Resolver es un término de tiempo de enlace. En el tiempo de enlace, el enlazador intenta encontrar la definición externa para cada símbolo en los archivos de objeto que no pueden encontrar su definición internamente. El alcance de este proceso de búsqueda incluye:

  • Todos los archivos de objetos que se generaron en tiempo de compilación.
  • Todas las bibliotecas (.lib) que se especifican explícita o implícitamente como dependencias adicionales de esta aplicación de construcción.

Este proceso de búsqueda se llama resolver.

D. Finalmente, ¿por qué símbolo externo no resuelto? Si el enlazador no puede encontrar la definición externa para un símbolo que no tiene una definición interna, informa un error de Símbolo externo no resuelto.

E. Posibles causas de LNK2019 : Error de símbolo externo no resuelto. Ya sabemos que este error se debe a que el vinculador no pudo encontrar la definición de los símbolos externos, las posibles causas se pueden clasificar de la siguiente manera:

  1. Definición existe

Por ejemplo, si tenemos una función llamada foo definida en a.cpp:

int foo()
{
    return 0;
}

En b.cpp queremos llamar a la función foo, así que agregamos

void foo();

para declarar la función foo () y llamarla en otro cuerpo de función, diga bar() :

void bar()
{
    foo();
}

Ahora, cuando construya este código, recibirá un error LNK2019 quejándose de que foo es un símbolo sin resolver. En este caso, sabemos que foo () tiene su definición en a.cpp, pero diferente de la que estamos llamando (valor de retorno diferente). Este es el caso que existe la definición.

  1. La definición no existe

Si queremos llamar a algunas funciones en una biblioteca, pero la biblioteca de importación no se agrega a la lista de dependencia adicional (establecida en: Project | Properties | Configuration Properties | Linker | Input | Additional Dependency ) de la configuración de su proyecto. Ahora el enlazador informará un LNK2019 ya que la definición no existe en el alcance de búsqueda actual.





c++ pointers reference c++-faq