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.;
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;
¡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;
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.;
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.;
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;
}
Dirección |
Valor |
Variable |
Ámbito |
---|---|---|---|
… |
|||
0x6AFEFC |
5 |
|
|
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;
}
Dirección |
Valor |
Variable |
Ámbito |
---|---|---|---|
… |
|||
0x6AFEFC |
5 |
|
|
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;
}
Dirección |
Valor |
Variable |
Ámbito |
---|---|---|---|
… |
|||
0x6AFEFC |
6 |
|
|
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;
}
Dirección |
Valor |
Variable |
Ámbito |
---|---|---|---|
… |
|||
0x6AFEFC |
6 |
|
|