Anuncios

En el post anterior hablamos sobre apuntadores, hoy vamos a ver el tema de referencias que es muy similar a los apuntadores. La referencia es un alias o sinónimo de lo que es el destino es decir, a partir de la creación de una referencia toma el lugar del destino y toda modificación hecha en la referencia, en realidad estaria haciendosela al destino.

Anuncios

Para crear una referencia, el operador utilizado es el ampersand (&) y como nomenclatura estandarizada entre los programadores a los objetos creados por referencia se los inicia con un r. Un ejemplo de inicio de referencia seria asi:

int unEntero;
int & rUnaRef = unEntero;
Anuncios

Veamos el primer ejemplo de como iniciar una referencia y como esta puede ser modificada:

ref00.cpp

# include <iostream>

using namespace std;

int main()
{
	int intUno;
	int & rUnaRef = intUno;
	intUno = 5;
	cout << "intUno: " << intUno << endl;
	cout << "rUnaRef: " << rUnaRef << endl;
	rUnaRef = 7;
	cout << "intUno: " << intUno << endl;
	cout << "rUnaRef: " << rUnaRef << endl;
	return 0;
}
Anuncios

Como pueden ver como es la iniciación de una referencia y esta es efectuada, cuando se modifica la misma en realidad esta se realiza sobre el destino (o sea a intUno), ahora si nosotros utilizaramos el operador de dirección de memoria en una referencia este nos devuelve la dirección de memoria del destino (si fuera el ejemplo anterior seria la dirección de intUno). Las referencias no se pueden reasignar, una vez que fue asignada se convierte en el alias del destino, se le puede modificar el valor pero no se puede modificar a quien fue asignado, si lo compilamos obtendremos una salida como se ve a continuacion:

tinchicus@dbn001dsk:~/lenguaje/c++$ ./ref00
intUno: 5
rUnaRef: 5
intUno: 7
rUnaRef: 7
tinchicus@dbn001dsk:~/lenguaje/c++$
Anuncios

En este ejemplo podemos ver como al modificar intUno modifica tambien a la referencia y si nosotros modificamos la referencia tambien se modifica el valor de la variable que sirve de origen a la referencia, ahora pasemos a otro ejemplo:

ref01.cpp

# include <iostream>

using namespace std;

int main()
{
	int intUno;
	int & rUnaRef = intUno;
	intUno = 5;
 	cout << "intUno:\t" << intUno << endl;
 	cout << "rUnaRef:\t" << rUnaRef << endl;
 	cout << "&intUno:\t" << &intUno << endl;
 	cout << "&rUnaRef:\t" << &rUnaRef << endl;
	int intDos = 8;
 	rUnaRef = intDos;
 	cout << "\nintDos:\t" << intDos << endl;
 	cout << "intUno:\t" << intUno << endl;
 	cout << "rUnaRef:\t" << rUnaRef << endl;
 	cout << "&intUno:\t" << &intUno << endl;
 	cout << "&intDos:\t" << &intDos << endl;
 	cout << "&rUnaRef:\t" << &rUnaRef << endl;
	return 0;
}
Anuncios

Como se puede ver en este ejemplo, generamos una referencia para la variable intUno, imprimimos los valores, y despues se genera una variable intDos que se la asignamos a la referencia, rUnaRef, esta operacion no modifica el destino de la referencia sino que modifica el valor del destino con el de esa variable, o sea que ahora intUno = intDos, les muestro como es la salida del programa, tengan en cuenta que las direcciones de memoria pueden variar porque eso depende de cada equipo:

tinchicus@dbn001dsk:~/lenguaje/c++$ ./ref01
intUno: 5
rUnaRef:        5
&intUno:        0xbf887508
&rUnaRef:       0xbf887508
intDos: 8
intUno: 8
rUnaRef:        8
&intUno:        0xbf887508
&intDos:        0xbf887504
&rUnaRef:       0xbf887508
tinchicus@dbn001dsk:~/lenguaje/c++$
Anuncios

Observen que las direcciones de memoria de intUno y rUnaRef siempre se mantienen iguales pero el valor varia cuando se le asigna la variable intDos. Las referencias pueden ser utilizadas desde variables hasta objetos, estas no pueden referenciar a un tipo o una clase, pero si a la variable de ese tipo o al objeto de esa clase, veamos un par de ejemplos:

Manera incorrecta de declarar:
int & rUnaRef = int; // para un tipo de variable.
Gato & rGatoRef = Gato; // para un tipo de clase
Manera correcta de declarar:
int unaVariable = 200;
int & rUnaRef = unaVariable; // para un tipo  

Gato Pelusa;  
Gato & rGatoRef = Pelusa; // para una clase 
Anuncios

Como ven la manera correcta de utilizar la referencia es apuntarlo a una variable (no a un tipo) y sino al objeto creado para una clase, a diferencia del apuntador la referencia no necesita ser declarado nulo cuando se lo elimina porque estas deben ir a algun valor, en el caso de que se les asignara un valor nulo, esto podria generar errores impredecibles que llevarian a salir de programa sin saber el motivo. Veamos el caso de las funciones, los apuntadores o referencias se pueden utilizar en estos. La ventaja es que a diferencia de como se utilizan las funciones, se pasan una copia de los valores a traves de los parametros, estos pueden ser referenciados y modificar directamente al mismo sin tener que ser duplicados y por ende llevados y traidos. Veamos un ejemplo sobre esto:

ref02.cpp

# include <iostream>

using namespace std;

void Intercambiar(int & x, int & y);

int main()
{
	int x=5,y=10;
	cout << "Main. Antes del intercambio, ";
	cout << "x: " << x << " y: " << y << endl;
 	Intercambiar(x, y);
 	cout << "Main. Despues del intercambio, ";
 	cout << "x: " << x << " y: " << y << endl;
	return 0;
}

void Intercambiar(int & rx, int & ry)
{
 	int temp;
	cout << "Intercambio. Antes del mismo, ";
 	cout << "rx: " << rx << " ry: " << ry << endl;
	temp=ry;
 	ry=rx;
 	rx=temp;
	cout << "Intercambio. Despues del mismo, ";
 	cout << "rx: " << rx << " ry: " << ry << endl;
}
Anuncios

Como se ve los parametros siguen existiendo pero ahora no se necesita hacer un duplicado como antes para poder ingresar esos valores dentro de la funcion, se hace una referencia a los mismos (ya que fueron declarados como parametros de la funcion) y esta en vez de efectuar ese procedimiento lo que realiza es utilizar el valor que se brinda a traves de la referencia sin generar informacion redundante en el programa y una vez modificada las referencias estas modifican a los parametros de destino (u origen) que se encuentran en el main. La salida del programa es la siguiente:

tinchicus@dbn001dsk:~/lenguaje/c++$ ./ref02
Main. Antes del intercambio, x: 5 y: 10
Intercambio. Antes del mismo, rx: 5 ry: 10
Intercambio. Despues del mismo, rx: 10 ry: 5
Main. Despues del intercambio, x: 10 y: 5
tinchicus@dbn001dsk:~/lenguaje/c++$
Anuncios

Este mismo programa se puede hacer con apuntadores, aca en este post les dejo un ejemplo de como seria, como vimos en el caso del programa en el encabezado del mismo declaramos que se utilicen referencias de x e y esto no solamente nos facilita una mejor utilizacion del programa sino que facilita a la depuracion del codigo dado que las variables son solo las de main y en el resto del programa solamente se utiliza las referencias de las mismas. Ahora que estamos mas avanzados, las funciones que vimos en los posts de funciones, siempre nos devolvian un solo valor a traves de return y nosotros teniamos que encadenar varios para obtener mas de una salida, con los apuntadores o las referencias esto se puede realizar de forma completamente distinta permitiendo no solo que tengamos mas de un valor a la salida de la funcion sino tambien permitiendo que return tenga un valor que pueda corresponder a una representacion de error porque ocurrio algo que no esta dentro de los parametros que definimos para el mismo, veamos otro ejemplo:

ref03.cpp

# include <iostream>

using namespace std;

typedef unsigned short USHORT;
enum CODIGO_ERR { EXITO, ERROR };

CODIGO_ERR Factor (USHORT, USHORT &, USHORT &);

int main()
{
 	USHORT numero, alcuadrado, alcubo;
 	CODIGO_ERR resultado;
	cout << "Escriba un valor entre 0-20: ";
 	cin >> numero;
	resultado = Factor(numero, alcuadrado, alcubo);
	if (resultado == EXITO)
 	{
 		cout << "Numero: " << numero << endl;
 		cout << "Al cuadrado: " << alcuadrado << endl;
 		cout << "Al cubo: " << alcubo << endl;
 	} else {
 		cout << "Se encontro un error!!!\n";
 	}
 	return 0;
}

CODIGO_ERR Factor(USHORT n, USHORT & rAlcuadrado, USHORT & rAlcubo)
{
 	if (n>20)
 	{
 		return ERROR;
 	} else {
 		rAlcuadrado = n * n;
 		rAlcubo = n * n * n;
 		return EXITO;
 	}
}
Anuncios

Un codigo simple donde debemos devolver el cuadrado y el cubo de un valor que no debe superar a 20, en ese caso el programa devuelve un error y no lo realiza. Analicemos un poco, primero creamos una variable sin signo y corta (USHORT), y luego enumeramos otra (CODIGO_ERR), lo que hacemos especialmente con la segunda es que se creen las variables EXITO, donde va a tener el valor 0, y luego ERROR que va a tener el valor 1,  y con este tipo de variable generamos la funcion Factor, que va a tener un valor y dos referencias, despues que ingresamos el valor a traves de resultado sabemos si funciono o no, el valor de resultado va a ser comparado por un IF, si es igual a ERROR se termina con el mensaje de error y si es EXITO nos devuelve los valores solicitados, cuando declaramos la funcion Factor, vemos que esta la variable n que es el valor de numero y las referencias de alcuadrado y alcubo donde la funcion verifica si n es mayor que 20 o no, si es mayor devuelve el codigo ERROR y sale de la funcion, en el caso de no ser mayor procede con la ejecucion para generar el valor cuadrado y cubo de n (o numero) y como quedan asignados a una referencia esta puede ser invocada desde main sin ningun inconveniente y como devuelve el valor de EXITO este al ser comparado por el IF de main nos permite ver los valores pedidos, veamos las salidas:

tinchicus@dbn001dsk:~/lenguaje/c++$ ./ref03
Escriba un valor entre 0-20: 21
Se encontro un error!!!
tinchicus@dbn001dsk:~/lenguaje/c++$ ./ref03
Escriba un valor entre 0-20: 10
Numero: 10
Al cuadrado: 100
Al cubo: 1000
tinchicus@dbn001dsk:~/lenguaje/c++$
Anuncios

Un problema que tenemos cuando se utilizan objetos, estos generan una copia del constructor y otra copia del destructor, si son varios objetos o se lo llama varias veces esto nos produce un desperdicio de tiempo (por el procesamiento) y tambien el mal uso de la memoria pila, aunque los objetos sean borrados por el destructor mientras estos se estan utilizando ocupan memoria. Con los ejemplos que vimos hasta ahora no genera ningun inconveniente dado que son datos muy chicos, pero con clases y objetos definidos por nosotros con mucha mas informacion podria generarnos inconvenientes mas graves, para evitar estos seria mejor utilizar a dichos objetos por medio de apuntadores, el unico inconveniente que nos puede generar este metodo es que la funcion que utiliza un apuntador o referencia puede ser modificada por una funcion y esto traeria algun error en el programa, para evitarlo se debe declarar a los mismos como const para justamente evitar que sean modificadas dentro de una funcion, veamos un ejemplo de todo lo que hablamos:

ref04.cpp

# include <iostream>

using namespace std;

class GatoSimple
{
public:
 	GatoSimple();
 	GatoSimple(GatoSimple &);
 	~GatoSimple();
 	int ObtenerEdad() const { return suEdad; }
 	void AsignarEdad(int edad) { suEdad = edad; }
private:
 	int suEdad;
};

GatoSimple::GatoSimple()
{
 	cout << "Constructor de GatoSimple....\n";
 	suEdad = 1;
}

GatoSimple::GatoSimple(GatoSimple &)
{
 	cout << "Contructor de copia de GatoSimple...\n";
}

GatoSimple::~GatoSimple()
{
 	cout << "Destructor de GatoSimple...\n";
}

const GatoSimple & FuncionDos(const GatoSimple & elGato);

int main()
{
 	cout << "Crear un Gato...\n";
 	GatoSimple Pelusa;
 	cout << "Pelusa tiene ";
 	cout << Pelusa.ObtenerEdad();
 	cout << " años de edad.\n";
 	int edad = 5;
 	Pelusa.AsignarEdad(edad);
 	cout << "Pelusa tiene " << Pelusa.ObtenerEdad() << " años de edad.\n";
 	cout << "Llamando a FuncionDos...\n";
 	FuncionDos(Pelusa);
 	cout << "Pelusa tiene " << Pelusa.ObtenerEdad() << " años de edad.\n";
 	return 0;
}

const GatoSimple & FuncionDos(const GatoSimple & elGato)
{
 	cout << "FuncionDos regresando ...\n";
 	cout << "Ahora Pelusa tiene " << elGato.ObtenerEdad() << " años de edad.\n";
 	return elGato;
}
Anuncios

Para este ejemplo utilizaremos una clase llamada GatoSimple, la cual tendra un constructor predeterminado, un constructor adicional, un destructor, una funcion para obtener el valor de edad (ObtenerEdad) y otro para asignar una nueva edad (AsignarEdad), por ultimo tendremos en la parte privada la variable de la edad (suEdad), nuestro siguiente sera definir al constructor predeterminado, en este caso mostrara un mensaje solamente haciendo referencia que es el constructor y asigna un valor a suEdad, luego definiremos el segundo constructor el cual recibe una referencia del tipo de la clase, donde solamente mostrara la un mensaje donde dice que es el constructor de copia, despues tendremos el destructor de la clase donde mostrara un mensaje, en los tres casos el mensaje es mostrado para darnos cuenta cuando son invocados, despues crearemos un prototipo de una funcion llamada FuncionDos del tipo GatoSimple donde tendra una referencia llamada elGato tambien de la clase GatoSimple pero esta funcion sera de tipo const y tambien debemos declarar como const a los atributos de la misma, despues tendremos el main donde crearemos un objeto de la clase GatoSimple llamado Pelusa, mostraremos la edad por medio de ObtenerEdad() y luego le asignaremos una nueva edad, AsignarEdad(), llamaremos a la FuncionDos() y por ultimo volveremos a mostrar la edad de Pelusa, por ultimo la funcion FuncionDos() recibe la referencia en elGato, muestra un mensaje y luego utiliza el metodo ObtenerEdad en base a la referencia informada a esta funcion, por ultimo retorna elGato, si lo compilamos y ejecutamos veremos la siguiente salida:

tinchicus@dbn001dsk:~/lenguaje/c++$ ./ref04
Crear un Gato…
Constructor de GatoSimple….
Pelusa tiene 1 años de edad.
Pelusa tiene 5 años de edad.
Llamando a FuncionDos…
FuncionDos regresando …
Ahora Pelusa tiene 5 años de edad.
Pelusa tiene 5 años de edad.
Destructor de GatoSimple…
tinchicus@dbn001dsk:~/lenguaje/c++$
Anuncios

Como dijimos antes, utilizando apuntadores o referencias se evitan que se generen constructores o destructores adicionales y en este caso todo se utiliza con referencias provocando que solamente se utilice uno solo y el resto a traves de la referencia.
Si vio el post anterior, estara pensando que las referencias son mejores que los apuntadores para esconder la informacion en el programa pero recuerden que estas no se pueden reasignar y tampoco pueden ser nulos, si hay alguna posibilidad de que el valor sea nulo se deberia utilizar un apuntador. Es posible mezclar apuntadores y referencias, otra cosa a tener en cuenta es no descuidar el origen de los objetos porque en caso de que se eliminara, quedaria huerfano y generaria un error de compilacion.

Como vimos las referencias pueden ser mas simples y mas efectivas que los apuntadores, los programadores las prefieren en gral., pero tambien tienen sus limitaciones. Lo mas correcto seria encontrar un equilibrio entre los apuntadores y las referencias, porque como dijimos antes las referencias no pueden ser nulas y tampoco reasignadas, en cambio los apuntadores no son susceptibles a estos problemas dado que se puedan crear y destruir las referencias del heap.

Anuncios

En resumen, hoy hemos visto las referencias, como son, para que se usan, distintos ejemplos, en cada uno de ellos como fuimos progresando y viendo sus pros y contras de los mismos, espero les haya sido util sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

Anuncios

Tengo un Patreon donde podes acceder de manera exclusiva a material para este blog antes de ser publicado, sigue los pasos del link para saber como.

Tambien podes donar

Es para mantenimiento del sitio, gracias!

$1.00