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


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

Pero, ¿cuáles son las diferencias?


Resumen de respuestas y enlaces a continuación:

  1. Un puntero se puede reasignar cualquier cantidad de veces, mientras que una referencia no se puede volver a asignar después del enlace.
  2. Los punteros no apuntan 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 existe una "aritmética de referencia" (pero puede tomar la dirección de un objeto señalado por una referencia y hacer una aritmética de puntero sobre ella como en &obj + 5)

Para aclarar un concepto erróneo:

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 completamente, asigna la misma cantidad de almacenamiento   como un puntero, y coloca la dirección   de i en ese almacenamiento.

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

Como regla general,

  • Use 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:


2617
2017-09-11 20:03


origen


Respuestas:


  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 y tamaño de memoria 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. Como 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 señala un puntero puede estar en la pila o en el montón. Lo mismo una referencia. Mi afirmación en esta afirmación no es que un puntero deba apuntar a la pila. Un puntero es solo una variable que contiene una dirección de memoria. Esta variable está en la pila. Como una referencia tiene su propio espacio en la pila, y dado que la dirección es la misma que la variable a la que hace referencia. Más en 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 indirección. Mientras que las referencias solo ofrecen un nivel de indirección.

    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 se puede asignar nullptr directamente, mientras que la referencia no puede. Si intenta lo suficiente y sabe cómo, puede hacer que la dirección de una referencia nullptr. Del mismo modo, si lo intentas lo suficiente, puedes tener una referencia a un puntero y luego 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 en una matriz, puede usar ++para ir al siguiente elemento al que apunta un puntero, y + 4 para ir al quinto elemento. Esto no importa de qué tamaño sea el objeto al que apunta el puntero.

  6. Un puntero necesita ser desreferenciado 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 utiliza -> para acceder a sus miembros, mientras que una referencia utiliza una ..

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

  8. Las referencias no pueden incluirse en una matriz, mientras que las referencias pueden ser (Mencionado por el usuario @litb)

  9. Las referencias de Const se pueden vincular 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 const& más seguro para su uso en listas de argumentos, etc.


1369
2018-02-27 21:26



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

UN referencia puede ser pensado como un puntero constante (¡no debe confundirse con un puntero a un valor constante!) con indirección automática, es decir, el compilador aplicará el * operador para ti.

Todas las referencias deben inicializarse con un valor no 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 referenciado en su lugar, ni es posible hacer aritmética en las referencias.

Los programadores de C pueden no estar de acuerdo con las referencias de C ++, ya que ya no será obvio cuando ocurra la indirección o si un argumento pasa por valor o por un puntero sin mirar las firmas de función.

Los programadores de C ++ pueden no estar de acuerdo con el uso de punteros ya que se consideran inseguros, aunque las referencias no son en realidad 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 la Preguntas frecuentes sobre C ++:

Aunque a menudo se implementa una referencia usando una dirección en el   lenguaje ensamblador subyacente, por favor hazlo no pensar en una referencia como una   puntero divertido mirando a un objeto. Una referencia es el objeto. Es   no un puntero al objeto, ni una copia del objeto. Eso es el   objeto.

Pero si una referencia De Verdad eran el objeto, ¿cómo podría haber referencias colgantes? En los idiomas no administrados, es imposible que las referencias sean 'más seguras' que los punteros: ¡generalmente no existe una forma de alias confiable de los valores dentro de los límites del alcance!

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

Viniendo de un fondo C, las referencias de C ++ pueden parecer un concepto un poco tonto, pero uno debe usarlas en lugar de punteros donde sea posible: direccionamiento automático es conveniente, y las referencias se vuelven especialmente útiles cuando se trata de RAII - pero no debido a ninguna ventaja de seguridad percibida, sino más bien porque hacen que la escritura del código idiomático sea menos incómoda.

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


301
2017-09-11 21:43



Si quieres ser realmente pedante, hay una cosa que puedes hacer con una referencia que no puedes hacer con un puntero: extender la vida útil de un objeto temporal. En C ++ si vincula una referencia constante a un objeto temporal, la duración 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. En realidad, es una referencia a un objeto temporal que ahora tiene la misma duración que la referencia.

Si prueba esto sin el const no debe compilar. No puede enlazar una referencia no constante a un objeto temporal, ni puede tomar su dirección para ese asunto.


151
2017-09-11 21:06



Al contrario de la opinión popular, es posible tener una referencia que sea NULA.

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

Por supuesto, es mucho más difícil hacerlo con una referencia, pero si lo logras, te arrancarás el pelo tratando de encontrarlo. Las referencias son no inherentemente seguro en C ++!

Técnicamente esto es un referencia inválida, no es una referencia nula C ++ no admite referencias nulas como concepto como puede encontrar en otros idiomas. Hay otros tipos de referencias inválidas también. Alguna referencia inválida aumenta el espectro de comportamiento indefinido, del mismo modo que usar un puntero no válido.

El error real está en la desreferenciación del puntero NULL, antes de la asignación a una referencia. Pero no conozco ningún compilador que genere ningún error en esa condición: el error se propaga a un punto más adelante en el código. Eso es lo que hace que este problema sea tan insidioso. La mayoría de las veces, si desreferencia un puntero NULL, se bloquea justo en ese punto y no se necesita mucha depuración para resolverlo.

Mi ejemplo anterior es corto y artificioso. Aquí hay un ejemplo más del mundo real.

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

Quiero reiterar que la única forma de obtener una referencia nula es a través del código mal formado, y una vez que lo tienes, obtienes un comportamiento indefinido. Eso Nunca tiene sentido verificar una referencia nula; por ejemplo, puedes probar if(&bar==NULL)... pero el compilador podría optimizar la declaración de la existencia! Una referencia válida nunca puede ser NULL, por lo tanto, desde la vista del compilador, la comparación siempre es falsa, y es libre de eliminar el if cláusula como código muerto: esta es la esencia del comportamiento indefinido.

La forma correcta de evitar problemas es evitar eliminar la referenciación de un puntero NULL para crear una referencia. Aquí hay una forma automática de lograr esto.

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

Para una visión más antigua de alguien con mejores habilidades de escritura, vea Referencias nulas de Jim Hyslop y Herb Sutter.

Para otro ejemplo de los peligros de desreferenciar un puntero nulo, vea Exponer comportamiento indefinido al intentar codificar el puerto en otra plataforma por Raymond Chen.


104
2017-09-11 22:10



Olvidaste la parte más importante:

acceso de miembros con usos de punteros -> 
acceso de miembros con usos de referencias .

foo.bar es claramente superior a foo->bar de la misma manera que vi es claramente superior a Emacs :-)


96
2017-09-11 20:07



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

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

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

El objetivo de una referencia no puede reemplazarse de ninguna manera excepto UB.

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


95
2017-09-19 12:23



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

Un compilador guarda "referencias" a 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 crea una referencia, solo le dice al compilador que asigna otro nombre a la variable del puntero; es por eso que las referencias no pueden "señalar nulo", porque una variable no puede ser ni 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 alguna explicación del código real:

int a = 0;
int& b = a;

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

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

int a;
increment(a);

Cuando se llama a una función, el compilador generalmente genera espacios de memoria para los argumentos a copiar. La firma de la función define los espacios que deben crearse y 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 código compilado, no hay más alcance; simplemente hay 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 extern. Entonces, 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.


57
2017-09-01 03:44