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

Sé que los enlaces 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?


Un resumen de las respuestas y enlaces a continuación:

  1. Un puntero se puede reasignar cualquier número de veces, mientras que un enlace no se puede reasignar después del enlace.
  2. Los punteros no pueden apuntar a ninguna parte ( NULL ), mientras que un enlace siempre se refiere a un objeto.
  3. No puede utilizar la dirección del enlace, como puede, punteros.
  4. No hay "aritmética de referencia" (pero puede tomar la dirección del objeto al que apunta el enlace y realizar una aritmética de punteros en él, como en + 5 ).

Para aclarar la falacia:

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

 int  = i; 

si no está totalmente optimizado , asigna la misma cantidad de memoria que el puntero y coloca la dirección i en este repositorio.

Por lo tanto, el puntero y el enlace utilizan 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.
  • Usa punteros para implementar algoritmos y estructuras de datos.

Lectura interesante:

2802
11 сент. set prakash 11 de septiembre 2008-09-11 23:03 '08 a las 11:03 pm 2008-09-11 23:03
@ 37 respuestas
  • 1
  • 2
  • El puntero puede ser reasignado:

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

    El enlace no puede y debe ser asignado durante la inicialización:

     int x = 5; int y = 6; int  = x; 
  • El puntero tiene su propia dirección y el tamaño de la memoria en la pila (4 bytes en x86), mientras que el enlace tiene la misma dirección de memoria (con la variable original), pero también ocupa algo de espacio en la pila. Dado que el enlace tiene la misma dirección que la variable original, es seguro pensar en el enlace como un nombre diferente para la misma variable. Nota Lo que apunta el puntero puede estar en la pila o en el montón. El mismo enlace. Mi afirmación en esta declaración no es que el puntero debe apuntar a la pila. Un puntero es simplemente una variable que contiene una dirección de memoria. Esta variable está en la pila. Porque el enlace tiene su propio espacio de pila, y como la dirección es la misma que la variable a la que se refiere. Lea más en la pila vs montón . Esto significa que hay una dirección de enlace real que el compilador no le dirá.

     int x = 0; int  = x; int *p =  int *p2 =  assert(p == p2); 
  • Puede tener punteros a punteros a punteros que ofrecen niveles adicionales de direccionamiento indirecto. Mientras que los enlaces ofrecen solo un nivel de direccionamiento indirecto.

     int x = 0; int y = 0; int *p =  int *q =  int **pp =  pp =  = q **pp = 4; assert(y == 4); assert(x == 0); 
  • El puntero puede asignarse directamente a nullptr , pero el enlace no puede. Si se está esforzando lo suficiente y sabe cómo hacerlo, puede hacer que la dirección del enlace sea nullptr . De manera similar, si se está esforzando lo suficiente, puede tener un enlace a un puntero, y ese enlace puede contener 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 
  • Los punteros pueden iterar sobre una matriz, puede usar ++ para ir al siguiente elemento apuntado por el puntero, y + 4 para ir al quinto elemento. Esto es independientemente del tamaño al que apunta el objeto.

  • El puntero se debe anular mediante el uso de * para acceder a la dirección de memoria a la que apunta, mientras que el enlace se puede usar directamente. Un puntero a la clase / estructura usa -> para acceder a ellos, mientras que el enlace usa . .

  • Un puntero es una variable que contiene una dirección de memoria. Independientemente de cómo se ejecute este enlace, el enlace tiene la misma dirección de memoria que el elemento al que se refiere.

  • Los enlaces no se pueden rellenar en una matriz, mientras que los punteros pueden ser (especificados por el usuario @litb)

  • Las referencias a Const pueden estar vinculadas a temporales. Los punteros no pueden (no sin ninguna indirecta):

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

    Esto hace que const> más seguro para su uso en listas de argumentos, etc.

1469
11 сент. La respuesta está dada por Brian R. Bondy 11 sep. 2008-09-11 23:08 '08 a las 11:08 pm 2008-09-11 23:08

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

Un enlace puede considerarse como un puntero constante (¡no debe confundirse con un puntero a un valor constante!) Con direccionamiento automático automático, es decir, el compilador utilizará el operador * para usted.

Todos los enlaces deben inicializarse con un valor distinto de cero, de lo contrario la compilación fallará. Es imposible obtener la dirección del enlace; en su lugar, el operador de la dirección devuelve la dirección del valor de referencia, y no es posible realizar una aritmética de enlaces.

Es posible que a los programadores de C no les gusten las referencias de C ++, ya que no será más obvio si se produce indirectidad o si el argumento se pasa por valor o puntero, sin mirar las firmas de la función.

Puede que a los programadores de C ++ no les guste el uso de punteros, ya que se consideran inseguros, aunque los enlaces en realidad no son más seguros que los punteros normales, excepto en los casos más triviales: no tienen la conveniencia de direccionamiento automático automático y tienen una connotación semántica diferente.

Considere la siguiente declaración de C ++ Preguntas frecuentes :

Incluso si el enlace se realiza a menudo utilizando la dirección en el lenguaje base del ensamblador, no piense en el enlace como un puntero de aspecto divertido a un objeto. Un enlace es un objeto. no es un puntero a un objeto, ni una copia de un objeto. Este es un objeto.

Pero si el enlace realmente era un objeto, ¿cómo podría haber enlaces rotos? En lenguajes no administrados, es imposible que los enlaces sean "más seguros" que los punteros. Como regla general, ¡esto simplemente no es una forma de alias confiable de los valores de los límites de alcance!

¿Por qué encuentro enlaces útiles en C ++?

Basándose en el fondo C, las referencias a C ++ pueden parecer un concepto un tanto tonto, pero aún así debe usarlas en lugar de punteros cuando sea posible: la indirección automática es conveniente, y los enlaces son especialmente útiles cuando se trabaja con RAII , pero no porque cualquier beneficio de seguridad percibido, sino por el hecho de que hacen que la letra 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 copia de la semántica. Pasar objetos por referencia le permite evitar estos problemas, ya que no se realiza la copia. Si los enlaces no están presentes en este idioma, tendrá que usar punteros, que son más complicados de usar, violando así el principio de desarrollar un lenguaje de que una solución con mejores prácticas debería ser más simple que las alternativas.

324
28 февр. Respuesta dada por Christoph el 28 de febrero. 2009-02-28 00:26 '09 a las 0:26 AM 2009-02-28 00:26

Si desea ser verdaderamente pedante, hay una cosa que puede hacer con un enlace 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, la vida útil de este objeto se convierte en la vida útil del enlace.

 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 un objeto temporal que es el resultado de la concatenación. Mientras que s3_reference se convierte esencialmente en un objeto temporal. Esto es realmente un enlace a un objeto temporal, que ahora tiene la misma duración que el enlace.

Si intenta esto sin const , no debería compilar. No puede enlazar un enlace no constante a un objeto temporal, y no puede aceptar su dirección a este respecto.

167
12 сент. Responder Matt Precio 12 sept. 2008-09-12 00:43 '08 a las 0:43 2008-09-12 00:43

Contrariamente a la creencia popular, es posible tener un enlace NULO.

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

Por supuesto, es mucho más difícil hacerlo con el enlace, pero si se enfrenta a esto, se rasgará el cabello, tratando de encontrarlo. Los enlaces de C ++ no son inherentemente seguros!

Técnicamente, esta es una referencia no válida , no una referencia nula. C ++ no admite enlaces nulos como concepto, como puede encontrar en otros idiomas. Hay otros enlaces inválidos. Cualquier enlace no válido mejora el rango de comportamientos indefinidos , así como el uso de un puntero no válido.

El error real está en eliminar la referencia del puntero NULL antes de asignar el enlace. Pero no conozco ningún compilador que genere errores bajo esta condición: el error se distribuye a un punto más en el código. Esto hace que este problema sea tan insidioso. En la mayoría de los casos, si toca el puntero NULO, jura directamente en este lugar, y no requiere mucha depuración para averiguarlo.

Mi ejemplo anterior es corto y artificial. Aquí hay un ejemplo más realista.

 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 repetir que la única forma de obtener un enlace nulo es el código incorrecto, y tan pronto como lo obtienes, obtendrás un comportamiento indefinido. Nunca tiene sentido verificar la referencia cero; por ejemplo, puedes probar if(> , ¡pero el compilador puede optimizar la declaración de la existencia! Una referencia válida nunca puede ser NULA, por lo que la comparación siempre es falsa en la representación del compilador, y puede excluir libremente la cláusula if como código muerto: esta es la esencia del comportamiento de indefinido.

La forma correcta de evitar problemas es evitar eliminar la referencia a un puntero nulo para crear una referencia. Aquí hay una forma automatizada de hacer 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 revisión más antigua de este problema para alguien que tiene las mejores habilidades de escritura, vea Referencias nulas de Jim Hyslop y Herb Sutter.

Para ver otro ejemplo del peligro de anular la referencia a un puntero nulo, consulte Visualización del comportamiento de indefinido al intentar transferir código a otra plataforma Raymond Chen.

114
12 сент. Respuesta dada por Mark Ransom 12 de septiembre 2008-09-12 00:06 '08 a las 0:06 2008-09-12 00:06

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

Actualización: ahora que lo pienso otra vez, hay una diferencia importante.

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

El enlace de destino no se puede reemplazar de ninguna manera por debajo de UB.

Esto debería permitir al compilador realizar una gran optimización por referencia.

111
11 сент. La respuesta se da Arkadiy 11 sep. 2008-09-11 23:07 '08 a las 11:07 pm 2008-09-11 23:07

Olvidaste la parte más importante:

acceso de miembros con punteros usa ->
Acceso de miembros con usos de enlaces .

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

105
12 сент. La respuesta es de Orion Edwards el 12 de septiembre. 2008-09-12 01:10 '08 a la 1:10 am 2008-09-12 01:10

En realidad, el enlace no parece un puntero.

El compilador almacena "referencias" a variables, asociando un nombre con una dirección de memoria; que su tarea es traducir cualquier nombre de variable a una dirección de memoria al compilar.

Cuando crea un enlace, le dice al compilador que está asignando un nombre de variable de puntero diferente; por qué los enlaces 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 cero. Es importante que el puntero tenga un valor y que el enlace tenga solo la variable a la que se refiere.

Ahora para una explicación del código real:

 int a = 0; int b = a; 

Aquí no creas otra variable que apunta a a ; simplemente agrega otro nombre al contenido de la memoria que contiene el valor a . Esta memoria ahora tiene dos nombres: a y b , y se puede resolver utilizando cualquier nombre.

 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 que se copiarán. La firma funcional define los espacios que se crearán y le da el nombre que se utilizará para estos espacios. Declarar un parámetro como referencia simplemente le dice al compilador que use el espacio con la variable de entrada variable 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 área de llamadas, pero recuerde que al ejecutar el código compilado, ya no hay ningún alcance; solo hay una memoria plana, y su código de función puede manipular cualquier variable.

Ahora puede haber ocasiones en que su compilador no sepa la referencia al compilar, por ejemplo, al usar la variable externa. Por lo tanto, el enlace puede o no implementarse como un puntero en el código base. Pero en los ejemplos que te di, lo más probable es que no se implemente con un puntero.

63
19 сент. la respuesta es dada por vicent roberto 19 sept. 2008-09-19 15:23 '08 a las 15:23 2008-09-19 15:23

Los enlaces son muy similares a los punteros, pero se crean específicamente para ayudar a optimizar los compiladores.

  • Las referencias están diseñadas de tal manera que el compilador simplifica enormemente el seguimiento de los alias de referencia, que son variables. Dos características importantes son muy importantes: no hay una "aritmética de referencia" ni una reasignación de referencias. Permiten al compilador descubrir qué enlaces contienen el alias, qué variables en el momento de la compilación.
  • Los enlaces pueden referirse a variables que no tienen direcciones de memoria, por ejemplo, que el compilador elige ingresar en los registros. Si toma la dirección de una variable local, el compilador es muy difícil de poner en un registro.

Como ejemplo:

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

El compilador optimizador puede comprender que estamos accediendo a [0] y [1] bastante. Sería deseable optimizar el algoritmo para que:

 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 } 

Para realizar esta optimización, es necesario probar que durante una llamada nada puede cambiar la matriz [1]. Es bastante fácil de hacer. Tengo al menos 2, por lo que la matriz [i] nunca puede referirse a una matriz [1]. maybeModify () se asigna a a0 como referencia (matriz de aliasing [0]). Como no hay una aritmética de "referencia", el compilador simplemente tiene que probar que es posible. Modificar nunca obtiene la dirección x, y demostró que nada cambia la matriz [1].

También tiene que demostrar que no hay forma de que una futura llamada pueda leer / escribir [0], mientras que tenemos una copia temporal del registro en a0. Esto es a menudo trivial de probar, porque en muchos casos es obvio que esta referencia nunca se almacena en una estructura permanente, como una instancia de una clase.

Ahora haz lo mismo con los punteros.

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

El comportamiento es el mismo; solo que ahora es mucho más difícil probar que, posiblemente, Modificar nunca modifica una matriz [1], porque ya le señalamos un puntero; El gato salió de la bolsa. Ahora tiene que hacer una prueba mucho más complicada: el análisis estático puede ser Modificado para probar que nunca escribe en x + 1. También tiene que probar que nunca elimina un puntero que puede referirse a una matriz [0] lo difícil que es

Los compiladores modernos están mejorando cada vez más con el análisis estático, pero siempre es bueno ayudarlos y usar los enlaces.

Por supuesto, con la excepción de tales optimizaciones inteligentes, los compiladores se referirán a referencias en punteros cuando sea necesario.

EDIT: Cinco años después de la publicación de esta respuesta, encontré una diferencia técnica real, donde los enlaces difieren de otra forma de ver el mismo concepto de direccionamiento. Los enlaces pueden cambiar la vida útil de los objetos temporales de tal manera que los punteros no pueden.

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

Por lo general, al final de una expresión, generalmente se destruyen los objetos temporales, como los que se crean al llamar a createF(5) . Sin embargo, al vincular este objeto con la referencia, ref , C ++ extenderá la vida útil de este objeto temporal hasta que ref vaya más allá.

62
01 сент. La respuesta se da a Cort Ammon 01 sep. 2013-09-01 06:44 '13 a las 6:44 2013-09-01 06:44

Un enlace nunca puede ser NULL .

37
11 сент. La respuesta es dada por RichS el 11 de septiembre. 2008-09-11 23:12 '08 a las 11:12 pm 2008-09-11 23:12

Aunque tanto los enlaces como los punteros se utilizan para acceder indirectamente a otro valor, existen dos diferencias importantes entre los enlaces y los punteros. Primero, el enlace siempre se refiere al objeto: el error está en la definición del enlace sin inicializarlo. El comportamiento de la asignación es la segunda diferencia importante: la asignación de un enlace reemplaza el objeto al que se adjunta el enlace; él no vuelve a comprobar el enlace a otro objeto. Después de la inicialización, el enlace siempre hace referencia al mismo objeto base.

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

 int ival = 1024, ival2 = 2048; int *pi =  *pi2 =  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, apuntando a otro objeto. Ahora considere un programa similar que asigna dos enlaces:

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