Anuncios

Hola, hoy veremos una nueva forma de manipular algunos tipos de funciones que hemos estudiado hasta ahora, entre los temas que veremos son: la sobrecarga de funciones miembro, como sobrecargar operadores y algunas cosas mas…. Empecemos con la sobrecarga de funciones.

Anuncios

La sobrecarga de funciones se trata basicamente de la posibilidad de tener multiples funciones con el mismo nombre pero que se diferencian con los parametros de las misma, por ejemplo ustedes pueden tener unas funciones asi:

void miFuncion();
void miFuncion(int var1);
void miFuncion(long var2, short var3);
Anuncios

Como ven las funciones se llaman iguales pero el programa las diferencia a traves de los tipos de parametros que utilizan cada una, y en base a esto llama a la funcion que corresponde. Este metodo de sobrecarga de funciones se podria hacer tambien utilizando valores predeterminados para una funcion, veamos un ejemplo de valores predeterminados y como variarla para la sobrecarga de funciones:

funcadv00.cpp

# include <iostream>

using namespace std;

class Rectangulo
{
public:
 	Rectangulo(int ancho,int altura);
 	~Rectangulo(){}
 	void DibujarFigura(int unAncho, int unaAltura, bool 	UsarValsActuales = false) const;
private:
 	int suAncho;
 	int suAltura;
};

Rectangulo::Rectangulo(int ancho, int altura):
 	suAncho(ancho),
 	suAltura(altura)
 	{}

void Rectangulo::DibujarFigura(int ancho, int altura, 
	bool UsarValActual) const
{
 	int imprimeAncho;
 	int imprimeAltura;
	if (UsarValActual == true)
 	{
 		imprimeAncho = suAncho;
 		imprimeAltura = suAltura;
 	} else {
 		imprimeAncho = ancho;
 		imprimeAltura = altura;
 	}
	for(int i = 0; i < imprimeAltura; i++)
 	{
 		for(int j = 0; j < imprimeAncho; j++)
 		{
 			cout << "*";
 		}
 	cout << endl;
 	}
}

int main()
{
 	Rectangulo elRect(30,5);
 	cout << "DibujarFigura(0,0,true)....\n";
 	elRect.DibujarFigura(0,0,true);
 	cout << "DibujarFigura(40,2)....\n";
 	elRect.DibujarFigura(40,2);
 	return 0;
}
Anuncios

Este es el ejemplo que vimos anteriormente en este post, mas exactamente en el caso de los ejemplos de for. Para este ejemplo, utilizamos un codigo similar pero con valores predeterminados. Cuando lo generamos en la clase, tenemos la funcion DibujarFigura tiene 3 valores (unAncho, unaAltura y un booleano UsarValsActuales) observemos al valor booleano, tiene definido un valor (false), esto significa que el valor booleano va a ser false cuando no lo declaremos. Observen al momento de crear el objeto le damos 2 valores (30 y 5), cuando llamamos por primera vez a DibujarFigura a estos los asignamos con los valores cero y cero pero en el booleano true haciendo que la funcion utilice los valores que definimos como predeterminados (en este caso si hubieramos enviado otros valores tambien habrian sido ignorados) dado que en el condicional espera un valor true para utilizar los parametros predeterminados. En la segunda llamada a DibujarFigura, vemos que tiene los valores cuarenta y dos pero no declara el valor de booleano dando como resultado que utilice los valores informados porque el booleano toma el valor false, recuerden que nosotros declaramos que el valor predeterminado era false para esta funcion, si lo compilamos la salida del programa sera la siguiente:

tinchicus@dbn001dsk:~/lenguaje/c++$ ./funcadv00
DibujarFigura(0,0,true)....
******************************
******************************
******************************
******************************
******************************
DibujarFigura(40,2)....
****************************************
****************************************
tinchicus@dbn001dsk:~/lenguaje/c++$
Anuncios

En este caso observamos claramente como primero dibujo con los parametros predeterminados la figura y luego con los parametros informados por nosotros, este mismo programa se podria hacer con sobrecarga de funciones, las modificaciones son muy pocas, pasemos a verlas:

En la linea:
void DibujarFigura(int unAncho, int unaAltura, bool UsarValsActuales = false) const;
Anuncios
Deberian ir estas dos lineas:
void DibujarFigura() const;
void DibujarFigura(int unAncho, int unaAltura) const;
Anuncios
el constructor:
Rectangulo::Rectangulo(int ancho, int altura):
	suAncho(ancho),
	suAltura(altura)
  	{}
Anuncios
Pasaria a ser asi:
Rectangulo::Rectangulo(int ancho, int altura)
  	{
  	suAncho=ancho;
  	suAltura=altura;
  	}
Anuncios
y en vez de utilizar esta funcion:
void Rectangulo::DibujarFigura(int ancho, int altura, 
	bool UsarValActual) const
{
  	int imprimeAncho;
  	int imprimeAltura;
	if (UsarValActual == true)
  	{
  		imprimeAncho = suAncho;
  		imprimeAltura = suAltura;
  	} else {
  		imprimeAncho = ancho;
  		imprimeAltura = altura;
  	}
}
Anuncios
Se deberia reemplazar con estas funciones:
void Rectangulo::DibujarFigura() const
{
  	DibujarFigura(suAncho, suAltura);
}
  
void Rectangulo::DibujarFigura(int ancho, int altura) const
{
  	for(int i = 0; i < altura; i++)
  	{
  		for(int j = 0; j < ancho; j++)
  		{
  			cout << "x";
  		}
  		cout << endl;
  	}
}
Anuncios
Por ultimo debemos modificar el cuerpo del main de la siguiente forma:
int main()
{
 	Rectangulo elRect(30,5);
 	cout << "DibujarFigura()....\n";
 	elRect.DibujarFigura();
 	cout << "DibujarFigura(40,2)....\n";
 	elRect.DibujarFigura(40,2);
 	return 0;
}
Anuncios

Con todas estas modificaciones nuestro codigo quedara de la siguiente forma:

funcadv01.cpp

# include <iostream>

using namespace std;

class Rectangulo
{
public:
 	Rectangulo(int ancho,int altura);
 	~Rectangulo(){}
 	void DibujarFigura() const;
	void DibujarFigura(int unAncho, int unaAltura) const;
private:
 	int suAncho;
 	int suAltura;
};

Rectangulo::Rectangulo(int ancho, int altura)
  	{
  	suAncho=ancho;
  	suAltura=altura;
  	}

void Rectangulo::DibujarFigura()
{
  	DibujarFigura(suAncho, suAltura);
}
  
void Rectangulo::DibujarFigura(int ancho, int altura) const
{
  	for(int i = 0; i < altura; i++)
  	{
  		for(int j = 0; j < ancho; j++)
  		{
  			cout << "x";
  		}
  		cout << endl;
  	}
}

int main()
{
 	Rectangulo elRect(30,5);
 	cout << "DibujarFigura()....\n";
 	elRect.DibujarFigura();
 	cout << "DibujarFigura(40,2)....\n";
 	elRect.DibujarFigura(40,2);
 	return 0;
}
Anuncios

Como vemos los dos programas hacen exactamente lo mismo pero como se ve en el segundo ejemplo, el de las sobrecargas, a simple vista puede ser mas explicito y facil de entender que en el ejemplo de las predeterminadas pero tambien nos conlleva a que el codigo se hace mas extenso porque tenemos una funcion para cada tipo de entrada de datos, y en el otro caso se puede definir con una condicion que verifica el valor booleano. En este ejemplo simple nos serviria mejor el uso de valores predeterminados pero para otros casos mas complejos para una mejor visualizacion nos servira la sobrecarga dado que las predeterminadas se van a volver mas complejas a medida que vayamos aprendiendo mas. Ahora, ud. se preguntara y como saber cuando deba utilizar una u otra, digamos que existe una regla empirica para utilizar las sobrecargas cuando no se cumplen estas tres condiciones:

Anuncios
  • Que no exista un valor predeterminado razonable.
  • Cuando necesite distintos algoritmos.
  • Siempre que necesite parametros de distintos tipos en las funciones o metodos.

Ya vimos valores predeterminados, ahora veamos constructores predeterminados. Los constructores predeterminados ya fueron mencionados cuando vimos el post de clases y objetos, en este se mencionaba que eran los que generaba el compilador cuando ud. no declaraba uno para la clase que hizo. Ahora, ud.  puede generarlos tambien sin parametros pero que lo ayude a “configurar” sus objetos a su necesidad. Como dijimos anteriormente por convencion el constructor generado por el compilador se llama predeterminado, a los constructores sin parametros se los llama de la misma forma. Un dato a tener en cuenta, es que el compilador no creara los constructores predeterminados si ud. declara algun otro, por lo que debera generar el constructor predeterminado ud. mismo. Como vinimos viendo hasta ahora a los constructores tambien se puede sobrecargarlos, y esta es una poderosa y flexible herramienta para multiples usos. Veamos un ejemplo para luego analizarlo:

funcadv02.cpp

# include <iostream>

using namespace std;

class Rectangulo
{
public:
 	Rectangulo();
 	Rectangulo(int ancho, int longitud);
 	~Rectangulo(){}
 	int ObtenerAncho() const { return suAncho; }
 	int ObtenerLongitud() const { return suLongitud; }
private:
 	int suAncho;
 	int suLongitud;
};

Rectangulo::Rectangulo()
{
 	suAncho = 5;
 	suLongitud = 10;
}
Rectangulo::Rectangulo (int ancho, int longitud)
{
 	suAncho = ancho;
 	suLongitud = longitud;
}

int main()
{
 	Rectangulo Rect1;
 	cout << "Ancho de Rect1: ";
 	cout << Rect1.ObtenerAncho() << endl;
 	cout << "Longitud de Rect1: ";
 	cout << Rect1.ObtenerLongitud() << endl << endl;
	int unAncho, unaLongitud;
 	cout << "Escriba un ancho: ";
 	cin >> unAncho;
 	cout << "Escriba una Longitud: ";
 	cin >> unaLongitud;
	Rectangulo Rect2(unAncho, unaLongitud);
 	cout << "\nAncho de Rect2: ";
 	cout << Rect2.ObtenerAncho() << endl;
 	cout << "Longitud de Rect2: ";
 	cout << Rect2.ObtenerLongitud() << endl;
	return 0;
}
Anuncios

En este ejemplo vemos que se crean dos constructores para Rectangulo, uno sin paramettros y otro con parametros, tambien hay dos funciones una para obtenerAncho y otro para obtenerLongitud que la unica funcion que tienen, valga la redundancia, es devolver el valor de suAncho y suLongitud respectivamente. Si observan el destructor es unico para los dos contructores. Veamos el primer constructor, sin parametros, tiene los valores de suAncho y suLongitud asignados y este va a devolver los valores que estan predeterminados en él. En el segundo constructor, vemos que tiene dos variables, y esos valores seran asignados a suAncho y suLongitud. Con las mismas funciones obtenemos los parametros de dos objetos distintos, esto nos permite tener una mayor flexibilidad con respecto a las funciones simplemente teniendo distintos constructores para la misma clase, compilemos y veamos como es la salida del codigo anterior:

tinchicus@dbn001dsk:~/lenguaje/c++$ ./funcadv02
Ancho de Rect1: 5
Longitud de Rect1: 10

Escriba un ancho: 20
Escriba una Longitud: 4

Ancho de Rect2: 20
Longitud de Rect2: 4
tinchicus@dbn001dsk:~/lenguaje/c++$
Anuncios

Ahora hablaremos de la inicializacion de variables en un constructor, vimos un ejemplo anteriormente, donde se declaraba el constructor de la siguiente forma:

Rectangulo::Rectangulo(int ancho, int altura):
      suAncho(ancho),
      suAltura(altura)
      {}
Anuncios

Este constructor posee sus inicializadores correspondientes para generar los valores predeterminados, para iniciarlos se declara el valor entre parentesis al lado de la variable, si se debe inicializar mas de una variable se separan con una coma (,). Veamos su estructura basica:

clase::clase():
      variable1(valor),
      variable2(valor),
      variable3(valor)
      {}
Anuncios

En este caso las variables no tienen la obligacion de que se inicialicen de esta forma, tambien pueden inicializarse en el cuerpo de la definicion del constructor pero en realidad de esta forma es un poco mas limpia y eficiente, obviamente esto siempre queda a criterio del programador. Hasta ahora sabemos que tenemos un constructor y un destructor predeterminado, tambien existe un constructor de copia predeterminado y es llamado cada vez que se crea la copia de un objeto. Todos los constructores de copia toman como referencia objetos de la misma clase, por eso es una buena practica definirlo como constante para que no pueda ser modificado cuando se pase, un ejemplo de esto es:

GATO (const GATO & elGato);
Anuncios

Como vemos el constructor toma como referencia una constante de la misma clase y el objetivo del constructor de copia es hacer una copia de elGato. Esto se llama copia de datos miembro (o superficial) que es la copia de cada variable del objeto que se pasa como parametro a las variables del nuevo objeto. Esto es correcto para la mayoria de las variables miembro pero con los apuntadores a objetos en el heap puede generar inconvenientes. Supongamos que GATO tiene una variable suEdad que apunta al heap, el constructor de copia predeterminado copiara la variable del nuevo GATO, un constructor de copia hace una copia exacta de todos los valores, y en el caso de los apuntadores tambien utilizan la misma direccion de memoria, suponiendo que realizamos esta accion, la variable suEdad del nuevo GATO apuntara a la misma direccion de suEdad del viejo GATO y como dijimos en apuntadores, el destructor se encargara de limpiar la memoria, en el caso del GATO original si llama al destructor libera esa memoria, y el nuevo GATO sigue apuntando a esa direccion de memoria y esta accion podria generar un apuntador perdido y por ende haria que el programa se vuelva completamente inestable. La solucion a este inconveniente es crear su propio conductor de copia y asignar la memoria segun sus necesidades. Esto se conoce como copia profunda. Veamos un ejemplo:

funcadv03.cpp

# include <iostream>

using namespace std;

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

GATO::GATO()
{
 	suEdad = new int;
 	suPeso = new int;
 	*suEdad = 5;
 	*suPeso = 9;
}
GATO::GATO(const GATO & rhs)
{
 	suEdad = new int;
 	suPeso = new int;
 	*suEdad = rhs.ObtenerEdad();
 	*suPeso = *(rhs.suPeso);
}
GATO::~GATO()
{
 	delete suEdad;
 	suEdad = NULL;
 	delete suPeso;
 	suPeso = NULL;
}
int main()
{
 	GATO pelusa;
 	cout << "Edad de pelusa: ";
 	cout << pelusa.ObtenerEdad() << endl;
 	cout << "Asignar la edad de pelusa en 6 ...\n";
 	pelusa.AsignarEdad(6);
 	cout << "Crear a Silvestre a partir de pelusa\n";
 	GATO Silvestre(pelusa);
 	cout << "Edad de pelusa: ";
 	cout << pelusa.ObtenerEdad() << endl;
 	cout << "Edad de Silvestre: ";
 	cout << Silvestre.ObtenerEdad() << endl;
 	cout << "Establecer edad de pelusa en 7...\n";
 	pelusa.AsignarEdad(7);
 	cout << "Edad de pelusa: ";
 	cout << pelusa.ObtenerEdad() << endl;
 	cout << "Edad de Silvestre: ";
 	cout << Silvestre.ObtenerEdad() << endl;
 	return 0;
}
Anuncios

Analicemos el ejemplo, generamos un constructor predeterminado y un constructor de copia:

GATO();   // Constructor
GATO(const GATO &);     // Constructor de copia profunda
Anuncios

Las variables miembro las declaramos como apuntadores y de valor entero, esto lo hacemos para observar como se manipula las mismas en la memoria heap. Iniciamos en el constructor el “aparcamiento” de la memoria para los valores int y le asignamos los valores, Este es el segmento que inicializa el constructor de copia:

GATO::GATO(const GATO & rhs)
{
 	suEdad = new int;
 	suPeso = new int;
 	*suEdad = rhs.ObtenerEdad();
 	*suPeso = *(rhs.suPeso);
}
Anuncios

Observe que el parametro es rhs, es comun referirse a un conductor de copia de esta forma dado que significa “lado derecho” (right-hand side),  observen que se generan en el heap el lugar para dos variables enteras, y despues se le asigna los valores que estan del “lado derecho” del igual. Observe que rhs se pasa del constructor de copia como referencia por ende tambien va a tener todas las propiedades de la clase GATO, porque tambien es de la clase GATO, y al pertenecer a la misma clase va a tener acceso a todas las funciones y variables miembro. En rhs.ObtenerEdad() devuelve el valor guardado en memoria a la que apunta suEdad para rhs. Observen que en el destructor eliminamos los apuntadores y tambien les asignamos un valor null para evitar futuros inconvenientes. Ahora observemos el programa, vamos al main. Primero creamos un objeto llamado pelusa, observen que el primer valor de edad de pelusa es el predeterminado, luego asignamos a pelusa la edad de seis. Ahora generamos un nuevo objeto llamado Silvestre que va a tener los mismos parametros que pelusa, cuando llamamos a la edad de ambos, nos devolvera seis en ambos casos, ahora continuamos y le asignamos a pelusa el valor siete, en este caso cuando volvemos a pedir los valores de pelusa y Silvestre, nos devolvera siete y seis respectivamente, el valor de pelusa no influye en Silvestre debido a que los apuntadores tienen distintos direcciones de memoria como referencia lo cual nos ayuda a tener un mejor control sobre nuestro datos, les paso la salida del programa para que lo vean:

tinchicus@dbn001dsk:~/lenguaje/c++$ ./funcadv03
Edad de pelusa: 5
Asignar la edad de pelusa en 6 …
Crear a Silvestre a partir de pelusa
Edad de pelusa: 6
Edad de Silvestre: 6
Establecer edad de pelusa en 7…
Edad de pelusa: 7
Edad de Silvestre: 6
tinchicus@dbn001dsk:~/lenguaje/c++$
Anuncios

Esto es solamente un ejemplo de como se puede utilizar distintas secciones del heap y poder evitar que queden apuntadores perdidos y nos deje el programa de forma inestable. Como vimos hasta ahora hay distintos tipos de datos integrado (int, long, short, etc), de estos hay varios tipos de operadores, suma (+) y multiplicacion (*). Tambien se pueden agregar estos operadores a las clases. Vamos a suponer esta situacion que necesitamos generar una clase que se llama Contador,  si lo hacemos como definimos las clases hasta ahora, no nos serviria como contador dado que los operadores no tendrian ningun efecto sobre los objetos que generamos, ahora si generamos un metodo que haga el incremento esta podria ser llamada pero igualmente todavia resulta algo incomoda su implementacion, para poder agilizarlo, deberiamos utilizar el prefijo ++, para poder utilizarlo en un metodo dentro de la clase se deberia declarar de esta forma:

tipodeRetorno operator op (parametros) 
Ejemplo: void operator ++ ();
Anuncios

Esto nos permitira utilizar el operador ++ en los objetos que creamos, esto es de gran ayuda y nos permite trabajar mejor sobre nuestras clases, veamos un ejemplo para poder entender mejor el concepto de operator:

operator.cpp

# include <iostream>

using namespace std;

class Contador
{
public:
 	Contador();
 	Contador(int val);
 	~Contador(){}
 	int ObtenerSuVal() const { return suVal; }
 	void AsignarSuVal(int x) { suVal=x; }
 	void Incremento() { ++suVal; }
 	Contador operator++ ();
private:
 	int suVal;
};

Contador::Contador():
 	suVal(0)
 	{}

Contador::Contador(int val):
 	suVal(val)
	{}

Contador Contador::operator++()
{
 	++suVal;
 	return Contador (suVal);
}

int main()
{
 	Contador i;
 	cout << "El valor de i es ";
 	cout << i.ObtenerSuVal() << endl;
 	i.Incremento();
 	cout << "El valor de i es ";
 	cout << i.ObtenerSuVal() << endl;
 	++i;
 	cout << "El valor de i es ";
 	cout << i.ObtenerSuVal() << endl;
 	Contador a = ++i;
 	cout << "El valor de a: " << a.ObtenerSuVal();
 	cout << " y el de i: " << i.ObtenerSuVal() << endl;
 	return 0;
}
Anuncios

Analicemos el ejemplo, aca tenemos una clase llamado Contador, vamos a generar dos constructores para Contador y tambien observen que vamos a declarar un operator para poder utilizar el prefijo ++:

 	Contador();  // -> Constructor predeterminado.
 	Contador(int val);  // -> Constructor que recibe un valor
 	~Contador(){}
 	int ObtenerSuVal() const { return suVal; }
 	void AsignarSuVal(int x) { suVal=x; }
 	void Incremento() { ++suVal; }
 	Contador operator++ ();  // -> prototipo de operator ++

Veamos a los dos constructores:

Contador::Contador():
 	suVal(0)
 	{}

Contador::Contador(int val):
 	suVal(val)
	{}
Anuncios

En ambos casos declaramos los valores predeterminados, en el que no tiene parametros le asignamos el valor 0 a suVal, en cambio para el otro constructor lo vamos a iniciar con el valor que le informemos en los parametros, pasemos al caso de operator:

Contador Contador::operator++()
{
 	++suVal;
 	return Contador (suVal);
}
Anuncios

En este caso lo primero que efectuamos es el incremento de suVal, recuerden que son miembros de la misma clase y por ende tiene acceso, y luego lo pasamos al Contador con los parametros.

Anuncios

Analicemos el main, la primera linea nos devuelve el valor predeterminado de i a traves de la clase Contador sin parametros, luego realizamos el primer aumento con la funcion Incremento, observamos que efectivamente hacemos la accion y ahora el valor de i paso a ser 1. En el proximo paso veamos como podemos hacer un incremento con el prefijo ++ a la clase i, vemos que cuando es invocado este efectua el aumento y por medio de Contador con parametros permite modificar el valor de suVal con el nuevo valor incrementado, cuando lo obtenemos lo vemos aumentado, en la siguiente linea vamos a crear un nuevo objeto para Contador llamado a y a este nuevo objeto le vamos a asignar el valor incremento del objeto i, cuando se recupera la informacion de a e i tienen exactamente el mismo valor y por supuesto incrementado, compilemos y veamos su salida:

tinchicus@dbn001dsk:~/lenguaje/c++$ ./operator
El valor de i es 0
El valor de i es 1
El valor de i es 2
El valor de a: 3 y el de i: 3
tinchicus@dbn001dsk:~/lenguaje/c++$

Anuncios

Como pueden ver esto es muy interesante para cuando generamos nuevas clases y estas necesitan incrementarse u operarse matematicamente. Para el ejemplo anterior tambien podriamos haber utilizado el apuntador this, como vimos en el caso anterior nosotros generamos un constructor “temporal” para poder realizar el incremento del objeto i cuando utilizamos al operator, con this nosotros tenemos que el apuntador tiene el valor de i y si es desreferenciado vamos a tener el acceso a suVal por lo que evitariamos la creacion de un constructor y funcion temporal, del caso anterior veamos como seria modificado con el this:

operthis.cpp

# include <iostream>

using namespace std;

class Contador
{
public:
 	Contador();
 	~Contador(){}
 	int ObtenerSuVal() const { return suVal; }
 	void AsignarSuVal(int x) { suVal=x; }
 	void Incremento() { ++suVal; }
 	const Contador  & operator++ ();
private:
 	int suVal;
};

Contador::Contador():
 	suVal(0)
	{}

const Contador & Contador::operator++()
{
 	++suVal;
  	return *this;
}

int main()
{
 	Contador i;
 	cout << "El valor de i es ";
 	cout << i.ObtenerSuVal() << endl;
 	i.Incremento();
 	cout << "El valor de i es ";
 	cout << i.ObtenerSuVal() << endl;
 	++i;
 	cout << "El valor de i es ";
 	cout << i.ObtenerSuVal() << endl;
 	Contador a = ++i;
 	cout << "El valor de a: " << a.ObtenerSuVal();
 	cout << " y el de i: " << i.ObtenerSuVal() << endl;
 	return 0;
}
Anuncios

La principal diferencia entre un codigo y otro es la desaparicion del segundo constructor, se usaba de forma temporal para asignar el nuevo valor de suVal incrementado por la funcion operator, tambien modificamos al metodo de operator, ya que gracias a this no necesitamos volver a referenciar a Contador sino por medio del mismo asigna el nuevo de suVal a la clase ya que posee acceso a la misma y eliminando las necesidades de generar constructores y metodos adicionales para manejar los datos “temporales”. Hasta aca vimos como utilizar un operador de prefijo, pero en algunos casos vamos a necesitar de posfijo, en este caso recordemos que la diferencia entre prefijo y posfijo es que en el caso de prefijo, “primero incrementa el valor y luego lo utiliza” para el caso de posfijo “primero lo utiliza y luego lo incrementa”. Supongamos como vinimos trabajando hasta ahora necesitariamos generar una funcion temporal que almacene el valor original antes de incrementarlo para luego devolverlo, el inconveniente mas importante que vamos a tener cuando termine la funcion no tendremos acceso a dicho valor si lo utilizamos por referencia por lo que debemos hacerlo atraves de un valor, para entender un poco mejor este concepto vamos a verlo por medio del sigueinte ejemplo:

operadv.cpp

# include <iostream>

using namespace std;

class Contador
{
public:
 	Contador();
 	~Contador(){}
 	int ObtenerSuVal() const { return suVal; }
 	void AsignarSuVal(int x) { suVal=x; }
 	void Incremento() { ++suVal; }
 	const Contador & operator++ ();
 	const Contador operator++ (int);
private:
 	int suVal;
};

Contador::Contador():
 	suVal(0)
 	{}

const Contador & Contador::operator++()
 	{
 	++suVal;
 	return *this;
 	}

const Contador Contador::operator++(int x)
 	{
 	Contador temp(*this);
 	++suVal;
 	return temp;
 	}

int main()
{
 	Contador i;
 	cout << "El valor de i es ";
 	cout << i.ObtenerSuVal() << endl;
 	i.Incremento();
 	cout << "El valor de i es ";
 	cout << i.ObtenerSuVal() << endl;
 	++i;
 	cout << "El valor de i es ";
 	cout << i.ObtenerSuVal() << endl;
 	Contador a = ++i;
 	cout << "El valor de a: " << a.ObtenerSuVal();
 	cout << " y el de i: " << i.ObtenerSuVal() << endl;
 	a = i++;
 	cout << "El valor de a: " << a.ObtenerSuVal();
 	cout << " y el de i: " << i.ObtenerSuVal() << endl;
 	return 0;
}
Anuncios

En este ejemplo vemos que tenemos las dos opciones, la de prefijo y posfijo, los metodos que se van a utilizar se los declara de esta forma:

 const Contador & operator++ ();  // Siendo el operator prefijo
 const Contador operator++ (int);  // Siendo el operator postfijo
Anuncios

Despues, es como el ejemplo anterior en todo lo referente al prefijo, pero para el caso del posfijo observermos que en la definicion del metodo para el posfijo, generamos un objeto temporal (temp) que va a tener el valor de this, que es el apuntador de la clase Contador, despues efectuamos el incremento pero devolvemos el valor de temp, que es de suVal antes de ser incrementado, lo cual genera el posfijo ya que vamos a tener un valor devuelto que es el anterior y a su vez el valor original incrementado, compilemos y veamos la salida del programa para explicarlo:

tinchicus@dbn001dsk:~/lenguaje/c++$ ./operadv
El valor de i es 0
El valor de i es 1
El valor de i es 2
El valor de a: 3 y el de i: 3
El valor de a: 3 y el de i: 4
 tinchicus@dbn001dsk:~/lenguaje/c++$
Anuncios

Noten que la ultima linea es la que nos interesa mas a nosotros porque es el resultado de hacer un incremento de posfijo al objeto i y asignandoselo al objeto a, observen el valor de a, es el valor de i sin incrementar, y luego i es el valor incrementado. Tambien se puede utilizar el operador de suma para los objetos que generamos, por ejemplo del caso anterior podriamos declarar unos objetos de esta forma:

Contador varUno, varDos, varTres;
varTres = varUno + varDos;
Anuncios

Para poder realizar esta operacion se deberia generar una funcion que produzca la suma de los dos objetos, pasemos a verlo con el siguiente ejemplo:

funcsuma.cpp

# include <iostream>

using namespace std;

class Contador
{
public:
 	Contador();
 	Contador(int valorInicial);
 	~Contador(){}
 	int ObtenerSuValor() const { return suVal; }
 	void AsignarSuValor(int x) { suVal = x; }
 	Contador Sumar(const Contador &);
private:
 	int suVal;
};

Contador::Contador(int valorInicial):
 	suVal(valorInicial)
 	{}

Contador::Contador():
 	suVal(0)
 	{}

Contador Contador::Sumar(const Contador & rhs)
{
 	return Contador(suVal + rhs.ObtenerSuValor());
}

int main()
{
 	Contador varUno(2), varDos(4), varTres;
	varTres = varUno.Sumar(varDos);
 	cout << "varUno: " << varUno.ObtenerSuValor() << endl;
 	cout << "varDos: " << varDos.ObtenerSuValor() << endl;
 	cout << "varTres: " << varTres.ObtenerSuValor() << endl;
 	return 0;
}
Anuncios

Aca tenemos dos constructores para Contador, uno sin parametros y otro con un valor inicial, y tambien agregamos un nuevo metodo que se llama Sumar, este va a ser el encargado de la suma de los dos objetos, analicemos el metodo Sumar:

 Contador Contador::Sumar(const Contador & rhs)
 {
      return Contador(suVal + rhs.ObtenerSuValor());
 } 
Anuncios

En este caso vemos al Contador definido como constante y un apuntador para rhs, vemos que retorna el valor de la suma de suVal, que es el valor asignado por el metodo que tiene parametros, y el valor del que esta declarado cuando se llama al metodo, en este caso varDos. Una vez llamado veremos que obtendremos esta salida:

tinchicus@dbn001dsk:~/lenguaje/c++$ ./funcsuma
varUno: 2
varDos: 4
varTres: 6
tinchicus@dbn001dsk:~/lenguaje/c++$
Anuncios

Observen que varUno y varDos, tienen los valores asignados pero varTres es la suma de los dos. Este metodo tambien se puede mejorar con el operator + para obtener un mejor metodo de suma de objetos, esto nos permitiria tener una mejor visualizacion y un mejor metodo de entendimiento para el codigo. Del caso anterior se debe reemplazar un par de lineas pero basicamente es exactamente lo mismo, veamos las lineas a modificar:

Linea del codigo:

Contador Sumar(const Contador &);

la debemos reemplazar por esta otra:

Contador operator+ (const Contador &);

Y cuando definamos el metodo lo unico que varia es:

Contador Contador::Sumar(const Contador & rhs)

por esta:

Contador Contador::operator+ (const Contador & rhs)

y por ultimo, debes modificar la linea dentro de main que produce la suma:

varTres = varUno.Sumar(varDos);

por esta ultima:

varTres = varUno + varDos;

Nuestro codigo final queda de la siguiente forma:

opersum.cpp

# include <iostream>

using namespace std;

class Contador
{
public:
 	Contador();
 	Contador(int valorInicial);
 	~Contador(){}
 	int ObtenerSuValor() const { return suVal; }
 	void AsignarSuValor(int x) { suVal = x; }
 	Contador operator+ (const Contador &);
private:
 	int suVal;
};

Contador::Contador(int valorInicial):
 	suVal(valorInicial)
 	{}

Contador::Contador():
 	suVal(0)
 	{}

Contador Contador::operator + (const Contador & rhs)
{
 	return Contador(suVal + rhs.ObtenerSuValor());
}

int main()
{
 	Contador varUno(2), varDos(4), varTres;
	varTres = varUno + varDos;
 	cout << "varUno: " << varUno.ObtenerSuValor() << endl;
 	cout << "varDos: " << varDos.ObtenerSuValor() << endl;
 	cout << "varTres: " << varTres.ObtenerSuValor() << endl;
 	return 0;
}
Anuncios

Si lo compilamos y probamos debemos obtener una salida similar al ejemplo anterior, con esto comprobamos que se puede hacer lo mismo con cualquier operador inclusive el de asignacion pero esto lo veremos en otro post. Tengan en cuenta que practicamente podemos volver a redefinir todas las funciones de un operador pero esto no tiene ningun sentido dado que no nos servira de mucho en el futuro hacer que la suma multiplique o la division saque la raiz cuadrada, lo ideal es que cada operador que asignemos haga la operacion que tiene asignada. Ahora finalmente veremos como son los operadores de conversion, nosotros en algun momento debemos asignar un valor de un tipo X a alguna clase que creamos nosotros, en esos casos podemos obtener errores porque la clase que creamos no es igual a la que asignamos, por ejemplo:

int valor = 5;
Contador varUno = valor;

Ahora para poder solucionar esto, veamos el siguiente ejemplo:

funcadv05.cpp

# include <iostream>

using namespace std;

class Contador
{
 	public:
 	Contador();
 	Contador(int val);
 	~Contador(){}
 	int ObtenerSuValor() const{ return suVal; }
 	int AsignarSuValor(int x) { suVal = x; }
private:
 	int suVal;
};

Contador::Contador():
 	suVal(0)
 	{}

Contador::Contador(int val):
 	suVal(val)
 	{}

int main()
{
 	int elShort = 5;
 	Contador Ctrl = elShort;
 	cout << "El Ctrl es: ";
 	cout << Ctrl.ObtenerSuValor() << endl;
 	return 0;
}
Anuncios

Como ven para que funcione el programa, se crean dos constructores, uno predeterminado y otro con un parametro del mismo orden que el que vamos a ingresar a nuestro objeto y despues se lo inicializa con el mismo a traves del operador de asignacion que vemos en el main:

Contador Ctrl = elShort;
Anuncios

Supongamos que queremos hacerlo al reves, que un valor entero reciba la clase que creamos nosotros, esto tambien creara un error de compilacion. El lenguaje provee un operador de conversion que se puede implementar en las clases, esto se hace a traves del operator, para ello debemos hacer una sola modificacion en la clase:

class Contador
{
 	public:
 	Contador();
 	Contador(int val);
 	~Contador(){}
 	int ObtenerSuValor() const{ return suVal; }
 	int AsignarSuValor(int x) { suVal = x; }
	operator unsigned short ();
private:
 	int suVal;
};
Anuncios

En este caso a la clase le agregamos una nueva linea que es la de operator y esta es solamente el prototipo y observen que es unsigned y short, nuestro siguiente paso sera definirlo:

Contador::operator unsigned short()
{
      return (int (suVal));
}
Anuncios

En este caso nos encargamos de devolver el mismo tipo de dato al que queremos enviarlo, para terminar el programa debemos hacer una modificacion en main:

int main()
{
 	Contador Ctrl(5);
 	int elShort = Ctrl;
 	cout << "El elShort es: ";
 	cout << elShort << endl;
 	return 0;
}
Anuncios

En este caso hacemos al reves primero crearemos un objeto llamado Ctrl y le asignamos el valor de 5, luego creamos una variable llamada elShort de tipo int y a este le asignamos el valor creado en Ctrl, y por ultimo en lugar de mostrar el valor de Ctrl mostramos el valor de elShort, si lo compilamos y ejecutamos nos devolvera la siguiente salida:

tinchicus@dbn001dsk:~/lenguaje/c++$ ./funcadv05
El elShort es: 5
tinchicus@dbn001dsk:~/lenguaje/c++$
Anuncios

En este caso observemos como se realizo con exito el paso de una clase generada por nosotros a un tipo primitivo del lenguaje, esto es una opcion que sucedera mas de lo que uno cree porque llegaremos a momentos que la informacion generado por una clase debera pasar a otra clase y evitar estos inconvenientes, antes de terminar veamos el codigo final:

funcadv05.cpp

# include <iostream>

using namespace std;

class Contador
{
 	public:
 	Contador();
 	Contador(int val);
 	~Contador(){}
 	int ObtenerSuValor() const{ return suVal; }
 	int AsignarSuValor(int x) { suVal = x; }
	operator unsigned short ();
private:
 	int suVal;
};

Contador::Contador():
 	suVal(0)
 	{}

Contador::Contador(int val):
 	suVal(val)
 	{}

Contador::operator unsigned short()
{
 	return (int (suVal));
}

int main()
{
 	Contador Ctrl(5);
 	int elShort = Ctrl;
 	cout << "El elShort es: ";
 	cout << elShort << endl;
 	return 0;
}
Anuncios

En resumen, hoy hemos visto las sobrecargas de funciones, como trabajar con distintos tipos de constructores, que es un constructor de copia, como inicializar los valores en un constructor, como usar los apuntadores, las referencias, hemos hablado de la referencia this, hemos visto como sobrecargar operadores y por ultimo un conversor simple de una clase a un tipo primitivo, 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