Paso por referencia

Para entender bien el paso por referencia de parámetros a una función, es conveniente, aunque sea de forma somera, introducir los conceptos de punteros y referencias.

Punteros

Cuando se define una variable, el sistema operativo reserva espacio en memoria capaz de almacenar el contenido de dicha variable.

double media = 2.;
../../_images/puntero1.jpg

En ciertas aplicaciones, es muy útil acceder indirectamente a la variable a través de su dirección.

Una variable tipo puntero es un tipo especial de dato, creado precisamente para almacenar la dirección en memoria de otra variable.

El formato general para la declaración de una variable puntero es:

tipo_de_variable_apuntada* nombre_del_puntero;

En el siguiente fragmento de código, p_media es un puntero que permitirá almacenar la dirección de memoria de una variable tipo double.

double media = 2.;
double* p_media;
../../_images/puntero2.jpg

¡El contenido de p_media es indeterminado!

Dado que un puntero almacena direcciones, el espacio requerido depende del hardware (4 bytes en un computador de 32 bits y 8 bytes en los de 64 bits).

Para trabajar con punteros se dispone de los operadores de puntero * y &.

El operador unario &

El operador unario & extrae la dirección de la variable a la que se le aplica.

double media = 2.;
double* p_media;
p_media = &media;
../../_images/puntero3.jpg

Ahora, p_media contiene la dirección de memoria donde se encuentra almacenada la variable media.

El operador unario *

El operador unario * accede al contenido de la variable cuya dirección de memoria es apuntada por el puntero, permitiendo su modificación.

double media = 2.;
double* p_media;
p_media = &media;
*p_media = 3.;
../../_images/puntero4.jpg

No trabajaremos con punteros durante el curso. Sin embargo, el concepto de dirección en memoria de una variable es muy importante en programación.

Hoy en día los punteros se utilizan casi en exclusiva por programadores avanzados.

Nota

Los operadores * y & son ejemplos de operadores sobrecargados. Como ocurre con otros operadores, su funcionalidad varía en función de:

  • a cuantos operandos afecta

  • cuales son los tipos de los operandos.

Referencias

Al igual que los punteros, las referencias son un tipo especial de variables que permiten alterar el contenido de otra variable. Sin embargo, el mecanismo es mucho más sencillo: una variable referencia actúa como un alias de la variable referenciada.

A diferencia de los punteros:

  • las referencias deben inicializarse en el momento de su declaración, es decir, indicar a qué variable referencian.

  • no pueden modificarse, es decir, cambiar la referencia a otra variable.

  • a efectos prácticos, el compilador sustituye implícitamente la variable referencia por la variable referenciada: no es necesario usar los operadores unarios * ó &.

El formato general para la definición de una variable referencia es:

tipo_de_variable_referenciada& nombre_referencia{variable_referenciada};

En el siguiente fragmento de código, r_media es una referencia, un alias de la variable media. A partir, de ese momento, el contenido de media puede modificarse usando r_media.

double media = 2.;
double& r_media{media};
r_media = 3.;
../../_images/puntero5.jpg

Uso de referencias como parámetros formales de una función

El uso de una referencia como parámetro formal de una función abre la puerta a poder modificar una variable local a una función desde una variable alias local a otra función.

El fundamento es que ambas variables, la original y su referencia, están asociadas a la misma dirección de memoria.

La sintaxis de uso de una referencia r como parámetro formal en una función es la siguiente:

tipo_funcion funcion(..., tipo_ref& r, ...);

Es importante notar que no se contraviene la obligación de que una referencia debe inicializarse con la variable referenciada. En el mismo momento en el que se produce la invocación a la función, el parámetro formal referencia se convierte en un alias de la variable real utilizada en la llamada.

Incremento del valor de una variable: traza del paso por referencia

Retomamos el ejemplo del paso por valor que usaba de forma errónea la función incrementa().

En este caso usaremos un paso por referencia.

Inicialización de la variable i local a main()

#include <iostream>
using namespace std;
void incrementa(int& i) // Nótese el uso de una referencia
{
  i = i + 1;
  cout << "Valor incrementado en funcion: " << i << endl;
}
int main()
{
  int i = 5;
  incrementa(i);
  cout << "Valor incrementado en main: " << i << endl;
}
Estado tras inicialización de i en main()

Dirección

Valor

Variable

Ámbito

0x6AFEFC

5

i

main()

Paso por referencia a la variable i local a incrementa()

#include <iostream>
using namespace std;
void incrementa(int& i) // Nótese el uso de una referencia
{
  i = i + 1;
  cout << "Valor incrementado en funcion: " << i << endl;
}
int main()
{
  int i = 5;
  incrementa(i);
  cout << "Valor incrementado en main: " << i << endl;
}
Estado tras paso por referencia a la variable i

Dirección

Valor

Variable

Ámbito

0x6AFEFC

5

i

i

main()

incrementa()

Nótese como ambas variables, real y formal, comparten la misma dirección de memoria.

Incremento de la variable i local a incrementa()

#include <iostream>
using namespace std;
void incrementa(int& i) // Nótese el uso de una referencia
{
  i = i + 1;
  cout << "Valor incrementado en funcion: " << i << endl;
}
int main()
{
  int i = 5;
  incrementa(i);
  cout << "Valor incrementado en main: " << i << endl;
}
Estado tras incremento de la variable i

Dirección

Valor

Variable

Ámbito

0x6AFEFC

6

i

i

main()

incrementa()

Salida de la función incrementa()

#include <iostream>
using namespace std;
void incrementa(int& i) // Nótese el uso de una referencia
{
  i = i + 1;
  cout << "Valor incrementado en funcion: " << i << endl;
}
int main()
{
  int i = 5;
  incrementa(i);
  cout << "Valor incrementado en main: " << i << endl;
}
Estado tras salida de la función incrementa()

Dirección

Valor

Variable

Ámbito

0x6AFEFC

6

i

main()