Referencias (programación)

Referencias (programación)
Información sobre la plantilla
Concepto:Son un tipo de dato estrechamente relacionado con los punteros. Es un recurso para pasar argumentos a funciones permitiendo que los argumentos no sean simples variables locales de la función, sino objetos del ámbito que realiza la invocación

Referencias (programación) . Son un tipo de dato estrechamente relacionado con los punteros. Una referencia de un objeto no es un objeto, en el sentido que no tiene su propio espacio de almacenamiento como ocurre con los punteros, y en consecuencia no pueden realizarse con ellas muchas de las operaciones que se relacionan con objetos. Por ejemplo obtener su dirección, crearlas con el operador nuevo, o crear matrices de referencias.

Una referencia es una especie de alias o "alter ego" del objeto. Es un recurso para pasar argumentos a funciones permitiendo que los argumentos no sean simples variables locales de la función, sino objetos del ámbito que realiza la invocación, lo que permite que la función pueda modificar objetos externos a ella.

Sintaxis:

La declaración de una variable de este tipo se realiza mediante el declarador de referencia. La sintaxis general es: <tipo_objeto> & <etiqueta_referencia> [= <iniciador>] Ejemplo: int x; ... int & z = x; // decimos que x es el 'iniciador' y que z es la 'referencia' Estas sentencias declaran e inicia la variable z como referencia-a-entero, y la asocia con la variable x (que es un entero). En adelante z actúa como un alias de x, de forma que cualquier operación sobre z equivale a hacerla sobre x. En realidad puede considerarse que z es "casi" un sinónimo de x (como si fuesen la misma variable).

Observaciones

En muchas situaciones prácticas puede ser indiferente utilizar una referencia o un puntero, sin embargo mantienen importantes diferencias que conviene conocer y que resumimos en el cuadro adjunto antes de comentarlas más detenidamente. En cualquier caso, la mejor regla es recordar que las referencias pueden considerarse como un sustituto del identificador de un objeto, mientras que los punteros deben ser considerados siempre como objetos en sí mismos. Además sus aritméticas son distintas.

Posibilidad de Referencias Punteros
Declaración independiente de la definición No Si
Asignarles un nuevo valor No Si
Referirse a un objeto de su mismo tipo No Si
Asignarles el valor void No Si

Declaración

Las referencias no pueden ser declaradas aisladas, de forma que tienen que estar indefectiblemente unidas a un objeto en su propia definición (deben ser inicializadas en la declaración). Además, una vez declaradas no pueden ser reasignadas a otro objeto (como los punteros), por lo que resultan unidas de por vida al objeto inicial. Por ejemplo: int& z; // Error. int& z = x // Ok. int& z = y; // Error. Por la razón anterior, puesto que tienen que estar unidas a un objeto, no pueden referenciar a void: int& z = void; // Error. Sí pueden ser inicializadas a otra referencia del mismo tipo, en cuyo caso señalan al objeto inicial.

Las referencias son una especie de punteros constantes, pero que no aceptan el álgebra de punteros ni pueden ser manipuladas igual.

Reasignación de referencias

Después de la definición inicial, las sucesivas asignaciones a las referencias deben ser con objetos del mismo tipo. Por ejemplo, a una referencia-a-int solo se le pueden asignar tipos int. Pero estas asignaciones son en realidad al objeto inicialmente referenciado. Ejemplo: int x = 10, y = 20; int& refi = x; // definición inicial cout << "X = " << refi; // -> X = 10 (valor de x) refi = y; // Ojo!! Equivale a: x = y cout << "X = " << refi; // -> X = 20 (x es ahora 20) cout << "X = " << x; // -> X = 20 (comprobación)

Modelado

En ocasiones el compilador realiza automáticamente determinadas promociones de tipo para hacer posible la asignación. Por ejemplo, siguiendo con las definiciones anteriores: enum COLOR { ROJO, VERDE, AZUL}; COLOR c1 = VERDE; refi = c1; // L.3: Ok!! (Ahora x = 1) cout << "X = " << refi; // -> X = 1 float f = 12.5; refi = f; // L.6: Ok!! cout << "F = " << f; // -> F = 12.5 cout << "X = " << refi; // -> X = 12 (Atención!!) En la asignación L.3, la variable enumerada c1 es promovida a entero (cosa perfectamente factible, el resultado es asignado a refi, lo que a la postre equivale a asignarlo a x. Un caso parecido es el de la asignación L.6, donde el float f es promovido a entero (lo que implica una pérdida de precisión) y el resultado asignado nuevamente a refi, la diferencia resultante entre f y x se muestra en las dos últimas salidas.

Punteros y referencias a referencias

No es posible declarar punteros-a-referencias ni referencias-a-referencias: int x = 10; int& rti = x; // Ok referencia-a-x int&* ptrti = &rti // Error!! Puntero-a-referencia int&& rar = rti // Error!! Referencia-a-referencia

Puesto que las referencias-a-tipoX son en realidad un "alter ego" del tipo referenciado, sí es posible utilizar referencias para la definición de otras referencias al mismo tipo: int& rti2 = rti; // Ok otra referencia-a-x En contra de lo que ocurre con sus parientes cercanos los punteros, no es posible iniciar referencias con el operador new.

Referencias a punteros

Aunque de poca importancia práctica, la referencia-a-puntero, es un alias que puede ser utilizado a todos los efectos como si fuese el propio puntero. Ejemplo:

  1. include <iostream.h>

int main() { // ====== int x = 10; int* ptr = &x; // puntero-a-int int*& ref = ptr; // referencia-a-puntero-a-int cout << "ptr-a-X = " << ref << endl; cout << "ptr-a-X = " << ptr << endl; cout << "Valor X = " << *ref << endl; // M.6 cout << "Valor X = " << *ptr << endl; } Salida: ptr-a-X = 0065FE00 ptr-a-X = 0065FE00 Valor X = 10 Valor X = 10 En este ejemplo es digna de mención la forma de declaración de ref, referencia-a-puntero; y como a todos los efectos, incluso para aplicarle el operador de in dirección *, la referencia se comporta en M.6 como un alias perfecto del puntero, señalando al mismo objeto que aquel.

Referencias a funciones

C++ permite definir referencias a funciones, aunque carecen de importancia práctica. La sintaxis para su declaración es la misma que con los punteros, aunque como es usual, su inicialización debe hacerse en el punto de la declaración. Así mismo, pueden invocarse funciones a través de sus referencias como si se tratara de punteros.

Si en la declaración de una referencia, el iniciador es una constante o un objeto de tipo diferente que el referenciado, entonces se crea un objeto temporal para el que la referencia actúa como un alias. Considere el siguiente ejemplo: int& z = 6; Se crea un objeto temporal tipo int que recibe el valor 6; se crea también una referencia-a-int de nemónico z, a ese objeto temporal. El compilador avisa de esta circunstancia con una advertencia: Temporary used to initialize 'z' in function.... el objeto temporal solo es accesible a través de su referencia.

Argumentos por referencia

La razón última de la introducción de referencias en C++ es posibilitar la sobrecarga de operadores, en la práctica su uso más frecuente es el paso de argumentos a funciones "por referencia"; en especial cuando se trata de objetos definidos por el usuario (instancias de clases). Observe que mientras en C clásico solo se pasan argumentos por valor, en C++ es posible pasar argumentos por valor y por referencia. Ejemplo: void func1 (int); // declara argumento de tipo int void func2 (int&); // declara argumento de tipo referencia-a-int ... int sum = 3; func1(sum); // sum pasa por valor func2(sum); // sum pasa por referencia

La utilización del argumento es idéntica en la invocación de ambas funciones, como si fuese "por valor". La diferencia en uno y otro caso estriba solo en la forma de declarar los argumentos en la definición de la función.

El argumento sum pasado por referencia en func2, puede ser modificado directamente desde dentro de esta función. Por contra, en func1 el argumento pasa por valor; la función recibe una copia de la variable sum, por lo que no puede modificar el valor original (la variable sum existente fuera de la función func1).

En ocasiones, especialmente cuando los argumentos son objetos (instancias de clases), el verdadero motivo de pasar objetos "por referencia" no es precisamente para que la función pueda modificar el argumento (incluso se intenta evitar esto declarando el argumento como referencia constante), sino por razones de eficacia del código. Más que alguna nueva "funcionalidad", el paso de argumentos a funciones "por referencia" solo proporciona cierta comodidad adicional a la funcionalidad proporcionada por los punteros

Valores devueltos por referencia

Otro uso común de este tipo de objetos es su utilización como valor de retorno de una función. Es el caso del valor devuelto por la función adjunta int& max (int& a, int& b) { if (a >= b) return a; return b; }

Criterios de eficiencia

En ocasiones la utilización de referencias para el paso de argumentos a funciones o en el valor devuelto, está motivada solo por criterios de eficacia. En efecto, las secuencias de llamada y retorno de funciones implican la creación de todas las variables locales de la función (incluyendo el valor que será devuelto), así como la invocación del constructor-copia para todos los argumentos que no han sido pasados por referencia. Cuando estos objetos son muy grandes. Por ejemplo, en instancias de clases con muchos datos o que derivan de jerarquías complejas, se requieren procesos de creación y destrucción, a veces muy complicados, que pueden evitarse parcialmente utilizando referencias. En tales casos suele recurrirse a declarar constantes los argumentos pasados "por referencia" para evitar que la función pueda alterar su valor.

Puede Consultar

Fuente