Clases
Las clases son una herramienta que provee C++ para la implementación de
tipos. A diferencia de lo que permitía gofer en la definición de tipos, en una
clase se pueden definir tanto la representación como las operaciones que se
pueden realizar, limitando la visibilidad
de las mismas.
Sintaxis:
class nombre_tipo
{
public:
nombre_tipo(); //
Contructor básico
nombre_tipo(parametros); // Constructor con parámetros
funciones
y variables públicas
<tipo> nombre_funcion
(parametros); //Una función o
método público
<tipo> nombre_variable; //Una
variable o propiedad pública
private:
funciones
y variable privadas
<tipo> nombre_funcion
(parametros); //Una función o
método privado
<tipo> nombre_variable; //Una
variable o propiedad privado
};
Los constructores son utilizados para inicializar los valores de la
estructura interna. Son llamados automáticamente cuando una variable del tipo nombre_tipo es declarada. Los
constructores pueden o no recibir parámetros y como en el ejemplo puede existir
más de uno. Se dice que una variable que se crea a partir de una definición de
clase, es una instancia de la clase.
En la parte pública se indican las funciones a las
que se desea que tenga acceso el usuario del tipo y conforman lo que se llama
interface de la clase. La interface de un tipo es una batería de funciones que
permiten al usuario del tipo utilizarlo para resolver sus propios problemas.
En la parte privada declaramos las variables que
conforman la estructura interna y las funciones auxiliares necesarias para la
implementación del tipo.
A las funciones que se
encuentran dentro de la clase se las llama métodos.
Para acceder a las variables o métodos declarados en una clase utilizamos el
punto ‘.’.
Ejemplo:
nombre_tipo
a; // a es de tipo nombre_tipo. En este momento se llama al constructor de la
clase.
a.nombre_variable_o_metodo: La forma de acceder a una variable o método
de a.
En este ejemplo la variable
a es una instancia de la clase nombre_tipo. Para implementar una clase
primero debemos declararla tal como lo indicamos arriba y luego debemos
implementar cada método que ha sido declarado.
Nota:
El punto y coma que cierra la declaración de la clase es fundamental, ya que si
no se pone el compilador no interpreta correctamente el código y dará errores
de compilación.
A continuación daremos un ejemplo para ilustrar una
implementación de un tipo simple.
Ejemplo
Veamos la definición e implementación del tipo Complejo tanto en Gofer como en C++
Gofer
data Complejo = C Float Float
crear_complejo :: Float -> Float
-> Complejo
crear_complejo r i = C r i
verReal :: Complejo -> Float
verReal (C r i) = r
verImag :: Complejo -> Float
verImag (C r i) = r
suma :: Complejo->Complejo ->
Complejo
suma (C r1 i1) (C r2 i2) = C (r1+r2) (i1+i2)
conjugado :: Complejo -> Complejo
conjugado (C r i) = C r (-i)
C++
COMPLEJO.H
#ifndef
COMPLEJO_H
#define
COMPLEJO_H
class
Complejo {
public:
Complejo();
Complejo(float r, float i);
Complejo(float r);
float
verReal() const; //Proyector de la parte real del complejo.
float
verImag() const; //Proyector de la parte imaginaria del complejo.
void
autoSuma(const Complejo &);
Complejo
suma(const Complejo &) const;
void
autoConjugado();
float
modulo() const; //Devuelve el módulo
del complejo.
float
angulo() const; //Devuelve el ángulo
del complejo.
private:
float
imag,real; //Variables internas
utilizadas para representar el tipo.
}; //Este punto y coma es fundamental no
olvidárselo.
#endif
En
esta declaración hay varios detalles para remarcar. La sentencia #ifndef COMPLEJO_H indica
al compilador (recuerden que las instrucciones que comienzan con # son para el
compilador) que chequee si está definida la variable de compilación COMPLEJO_H.
La segunda instrucción le indica al compilador que debe definir la variable de
compilación llamada COMPLEJO_H. Este “truco” es necesario para que cuando se
utiliza el tipo complejo
desde otros programas o módulos, no haya redefiniciones, ya que la primera vez
que el compilador define la variable, nunca más volverá a pasar por las
instrucciones que se encuentran entre #ifndef y #endif.
Ya
dentro de la definición del tipo hay algunos detalles interesantes para
destacar. La clase cuenta con tres constructores distintos. El primero no
recibe parámetros y su función sería declarar al complejo con su parte real e
imaginaria en 0. El segundo recibe dos números de punto flotante que
representan la parte real e imaginaria respectivamente con los cuales se
inicializarán las variables internas del objeto. El tercero recibe un solo
parámetro de tipo punto flotante que representa la parte real del complejo, en
este caso el constructor deberá inicializar la parte real con este número y
dejará la parte imaginaria en 0.
La
clase tiene dos proyectores que se utilizan para acceder a la parte real e
imaginaria del complejo. La necesidad de incluir proyectores y no dar acceso
público a las variables internas proviene de dar la posibilidad de cambiar la
representación del tipo sin necesidad de cambiar los programas que lo utilizan.
Por ejemplo si se piden cambiar la representación interna a número de notación
polar (módulo y ángulo), el hecho de no haber dado acceso a la representación
interna nos permite cambiar solamente los proyectores y las funciones propias
del tipo sin tener que modificar ninguno de los programas que lo usan, ya que
para el usuario del tipo este cambio será transparente.
Las
demás funciones que tiene el tipo son las siguientes:
¨ autoSuma:
que suma al parámetro implícito (aquel que recibe el llamado) el número
complejo pasado por parámetro. Noten que no devuelve ningún resultado, sino que
se modifica internamente para representar la suma con el número pasado por
parámetro, además el parámetro se pasa por referencia constante, lo que impide
modificarlo.
¨ suma:
en este caso se debe devolver la suma del número pasado por parámetro más el
que recibe el llamado. Esta función no modifica la representación interna, esto
se nota colocando al final de la declaración de la función la palabra const.
¨ autoConjugado:
Aplica la operación conjugado al parámetro implícito, esta función no recibe
otros parámetros ni devuelve resultado alguno, simplemente modifica la
estructura interna para representar que el número complejo luego de aplicado
este método es el conjugado del original.
¨ modulo:
debe devolver el módulo del complejo.
¨ angulo:
debe devolver el ángulo del complejo.
En
la implementación de la clase, si se recibe un parámetro del mismo tipo (como
sucede en la función suma), se puede acceder a la estructura
interna del objeto recibido. Por ejemplo, supongamos que tenemos que
implementar un nuevo constructor de la clase complejo:
Complejo(const
Complejo&); //Constructor que inicializa las variables internas a partir de
otro nro.
La
implementación de este constructor sería la siguiente:
Complejo::Complejo(const
Complejo & nro)
{
real=nro.real;
imag=nro.imag;
}
De
esta manera estamos accediendo directamente a las variables privadas del objeto
nro. En este caso, también se podrían haber utilizado los proyectores que
ofrece el tipo, pero hay otras ocasiones en que lo pedido en la función no
puede hacerse solo con los proyectores.
Para
guiarse en la implementación de una clase siempre se puede utilizar (si la
tienen) la solución gofer a modo de guía para resolver el problema.
El
ejercicio que les queda por resolver es implementar todas las funciones de la
clase complejo que se nombran en la declaración.