Daniel Garcia

sábado, julio 17, 2010

Descomposicion en modulos EN C

 

Vamos a separar nuestro ejemplo en tres ficheros:

· Caja.h, que contiene la definición de la clase

· Caja.cxx, que contiene la implementación de los métodos declarados en la clase Caja

· main.cxx, la función principal.

Fichero Caja.h:

class Caja {

double longitud, anchura, altura;

public:

Caja (double dim1, double dim2, double dim3);

Caja (void);

void set (double nuevaLongitud, double nuevaAnchura, double nuevaAltura);

void print (void);

double getLongitud (void);

double volumen (void) {return longitud * anchura * altura};

~Caja (void);

};

En Caja.h hemos incluido sólo la definición de la clase. No se dan detalles sobre las diversas funciones. Es decir, tenemos la definición completa de cómo utilizar una clase sin detalles de implementación. Este fichero no puede ser compilado ni ejecutado.

Fichero Caja.cpp:

# include 'Caja.h'

Caja :: Caja (double dim1, double dim2, double dim3) {

longitud = dim1;

anchura = dim2;

altura = dim3;

};

Caja :: Caja (void) {

longitud = 8;

anchura = 8;

altura = 8;

};

void Caja :: set (double nuevaLongitud,nuevaLongitud, double nuevaAnchura, double nuevaAltura) {

longitud = nuevaLongitud;

anchura = nuevaAnchura;

altura = nuevaAltura;

}

void Caja :: print (void) {

cout << 'longitud = ' << longitud <<'\n';

cout << 'anchura = ' << anchura <<'\n';

cout << 'altura = ' << altura <<'\n';

};

double Caja:: getLongitud (void) {

return longitud;

};

Caja::~Caja (void) {

longitud = 0; anchura = 0; altura = 0;

}

Vemos que:

· Se incluye el fichero cabecera Caja.h, que contiene los prototipos de las funciones.

· Caja.cpp contiene la implementación de las funciones declaradas en la clase Caja.

Este fichero puede ser compilado, pero no ejecutado, porque no tiene función main. Cuando sea compilado, el código objeto será almacenado, y estará disponible para usarlo por otros programas.

La separación de la definición y la implementación es un paso importante hacia la ingeniería de software. El fichero que contiene la definición es todo lo que el usuario necesita para utilizar la clase en un programa. No necesita conocer la implementación real de las funciones.

Fichero main.cpp:

#include <iostream.h>

#include 'Caja.h'

main () {

Caja pequeña(5, 4, 10), mediana (10, 6, 20), grande(20, 10, 30);

cout << 'El volumen de la caja grande es ' << grande.volumen() << '\n';

}

 

ARRAY DE OBJETOS EN C

Un array de objetos.

De la misma forma que declaramos vectores cuyos elementos son los tipos definidos en C++ (int, float, double, ...) podemos definir vectores formados por objetos definidos por el usuario. Veamos un ejemplo, partiendo de la clase Caja.

# include <iostream.h>

# include ' Caja.h'

main () {

Caja pequeña(5, 4, 10), mediana(10, 6, 20), grande, varias [4];

grande.set (20, 10, 30);

for (int indice = 1; indice < 4; indice ++)

varias [indice].set(indice + 10, 10, 10);

cout << ' El volumen de la caja pequeña es ' << pequeña.volumen() <<'\n';

cout << ' El volumen de la caja mediana es ' << mediana.volumen() <<'\n';

cout << ' El volumen de la caja grande es ' << grande.volumen() <<'\n';

for (indice = 0; indice < 4; indice ++)

cout << ' El volumen del array de cajas es' << varias[indice].volumen() <<'\n';

}

El resultado de la ejecución de este programa será:

El volumen de la caja pequeña es 200

El volumen de la caja mediana es 1200

El volumen de la caja grande es 6000

El volumen del array de cajas es 512

El volumen del array de cajas es 1100

El volumen del array de cajas es 1200

El volumen del array de cajas es 1300

Declaramos varias, un array formado por cuatro objetos del tipo Caja. Al hacer esta declaración, estamos llamando al constructor para cada uno de los cuatro objetos. Para declarar un array de objetos, debe existir un constructor para ese objeto que no reciba parámetros.

El contador del bucle for, indice, toma 1 como valor inicial, dejando que el primer objeto, varias [0], tome los valores por defecto (todas las dimensiones iguales a 8). Dentro del bucle, se llama a la función set para dar valor a las dimensiones de cada objeto. Esta construcción es similar a la de los objetos normales.

La variable indice se declara en el primer bucle, y está todavía disponible para su uso en el bucle de impresión, ya que no hemos salido del bloque en el que se declaró, la función main.

CLASES STATIC EN C

Ejemplo:

# include <iostream.h>

class Ejemplo {

int ejemplo1;

static int ejemplo2;

public:

Ejemplo (void);

void print(void);

};

int Ejemplo :: ejemplo2;

Ejemplo :: Ejemplo (void) {

ejemplo1 = 1;

ejemplo2 = 1;

}

void Ejemplo :: print (void) {

ejemplo1++;

ejemplo2++;

cout << 'ejemplo1 = ' << ejemplo1 << '\n';

cout << 'ejemplo2 = ' << ejemplo2 << '\n'; ;

}

main() {

Ejemplo primero, segundo;

primero.print();

segundo.print();

}

La salida de este programa es:

ejemplo1 = 2

ejemplo2 = 2

ejemplo1 = 2

ejemplo2 = 3

Una variable declarada static (ejemplo2) es una variable externa y sólo puede existir una copia de esa variable. Todos los objetos de esta clase (en este caso, primero y segundo) comparten una misma copia de esta variable, que es global a estos objetos.

En la definición de clase, la variable sólo es declarada. La declaración dice que la variable existirá y le da un nombre, pero la definición es la que realmente define un lugar para guardarla en la memoria del ordenador. Por definición, una variable puede ser declarada en la cabecera del fichero, pero no definida allí, sino fuera de ella, normalmente en el fichero de implementación.

El constructor inicializa las dos variables internas a 1 cada vez que se crea un objeto. Para mostrar que ejemplo2 es compartida por todos los objetos de esta clase, definimos una función, print, que incrementa el valor de las variables internas y a continuación las imprime.

Un objeto con un puntero interno

Modifiquemos la clase Caja para que incluya un puntero a una variable entera como atributo privado:

#include <iostream.h>

class Caja {

double longitud, anchura, altura;

int *point;

public:

Caja(double dim1, double dim2, double dim3, int valorAlmacenado);

~Caja();

double volumen(void) {return longitud * anchura * altura;} // Inline

int getValor(void) {return *point;} // Inline

};

//implementación del constructor

Caja::Caja(double dim1, double dim2, double dim3, int valorAlmacenado) {

longitud = dim1;

anchura = dim2;

altura = dim3;

point = new int;

*point = valorAlmacenado;

}

Caja::~Caja(void) { //Destructor

delete point;

}

main() {

Caja pequeña(5, 4, 10, 1), mediana (10, 6, 20, 2), grande(20, 10, 30, 3);

cout << 'El volumen de la caja pequeña es ' << pequeña.volumen() << '\n';

cout << 'El volumen de la caja mediana es' << mediana.volumen() << '\n';

cout << 'El volumen de la caja grande es' << grande.volumen() << '\n';

cout << 'El valor almacenado en la caja pequeña es ' << pequeña.getValor() << '\n';

cout << 'El valor almacenado en la caja mediana es' << mediana.get_valor() << '\n';

cout << 'El valor almacenado en la caja grande es ' << grande.get_valor() << '\n';

}

El resultado de la ejecución será:

El volumen de la caja pequeña es 200

El volumen de la caja mediana es 1200

El volumen de la caja grande es 6000

El valor almacenado en la caja pequeña es 1

El valor almacenado en la caja mediana es 2

El valor almacenado en la caja grande es 3

· Declaramos como atributo privado de la clase point, un puntero a un entero. Pero en la declaración no estamos asociando memoria a este puntero. Es en el constructor donde dinámicamente reservamos memoria para un entero, utilizando el operador new.

· Declaramos tres objetos, pequeña, mediana, y grande, del tipo Caja. Cada uno de ellos contiene un puntero que apunta a tres localizaciones de memoria diferentes. Cada objeto tiene su propia variable dinámicamente reservada para su uso privado. Además, en esta variable dinámicamente reservada se almacena el valor entero pasado al constructor.

· En un programa pequeño como éste, no se agotará la memoria, por tanto no es necesario comprobar que existe memoria disponible. En programas largos, sería conveniente comprobar que el valor del puntero devuelto no es NULL, para asegurar que los datos están realmente reservados.

· Hemos definido un destructor que borra con delete la memoria reservada dinámicamente con new. El destructor es llamado cuando los objetos que hemos definido (pequeña, mediana y grande) abandonan el bloque en el que han sido definidos. Si no hubiésemos definido el destructor, al salir de la función, quedarían en la memoria las tres variables reservadas dinámicamente sin nada apuntando a ellas. Por esta razón, el destructor se utiliza para borrar la variable a la que el puntero apunta cuando cada objeto sale de su ámbito de definición.

· En este caso particular, las variables serían automáticamente liberadas al volver al sistema operativo. Pero si se tratara de una función que llama a otra función, estaríamos llenando la memoria.

· Recordemos que si hubiésemos reservado memoria para más de un entero, por ejemplo:

p = new int [4];

en el destructor la liberaríamos de la siguiente forma:

delete [] p;

· Mencionemos de nuevo que las funciones declaradas inline deben ser utilizadas cuando la rapidez es lo más importante en el programa, ya que el código de la función se reescribe en el lugar en que se utiliza, y no se produce una llamada a una función definida de forma independiente. Definimos entonces el código como parte de la declaración de la clase, no en la implementación de las funciones. Si el código de la función es demasiado largo, el compilador puede ignorar el requisito de inline y tratarla como un método implementado independientemente, pero lo hará de forma invisible al usuario. Las funciones inline violan el principio de protección de la información, ya que el se hace el código de la función visible al usuario.

 

PUNTEROS A CLASES EN C

Punteros a clases.

Como cualquier otro tipo de dato, podemos tener punteros a clases, punteros a punteros a clases, punteros a punteros a punteros a clases, etc. Veamos un ejemplo:

# include <iostream.h>

# include 'Caja.h'

main() {

Caja grande (20, 10, 30);

Caja *punteroACaja1;

Caja *punteorACaja2;

punteroACaja1 = new Caja;

punteroACaja2 = new Caja(1, 2, 3);

cout << 'El volumen de la caja grande es ' << grande.volumen() << '\n';

cout << 'El nuevo volumen 2 es ' << punteroACaja2 -> volumen() << '\n';

punteroACaja1 -> set (2, 4, 6);

cout << 'El nuevo volumen 1 es ' << punteroACaja1 -> volumen() << '\n';

delete punteroACaja1, punteroACaja2;

}

La salida de este programa será:

El volumen de la caja grande es 6000

El nuevo volumen 2 es 6

El nuevo volumen 1 es 48

En este ejemplo hemos declarado dos punteros, punteroACaja1 y punteroACaja2, a objetos del tipo Caja. Hemos reservado memoria para la clase utilizando new, y al final de la función la hemos liberado utilizando delete.

Cuando declaramos punteroACaja1 y punteroACaja2 no se llama al constructor de Caja. Esto se hace cuando reservamos memoria para un objeto de ese tipo.

El acceso a los componentes del objeto se hace a través del operador ->

Las siguientes sentencias son equivalentes:

punteroACaja1 -> volumen()

(*punteroACaja1).volumen()

pero la notación -> es la que se suele utilizar.

Si la clase tuviese un destructor, éste sería llamado automáticamente cuando liberamos la memoria reservada dinámicamente para punteroACaja1 y punteroACaja2

OBJETOS ENCADENADOS EN C

Un objeto con un puntero a otro objeto: Objetos encadenados.

Añadamos a la clase Caja como atributo privado un puntero a otro objeto de la misma clase:

#include <iostream.h>

class Caja {

double longitud, anchura, altura;

Caja *otraCaja;

public:

Caja(double dim1, double dim2, double dim3); //Constructor

double volumen(void);

void point_at_next(Caja *where_to_point);

Caja *get_next(void);

};

Caja::Caja(double dim1, double dim2, double dim3) { //implementación del constructor

longitud = dim1;

anchura = dim2;

altura = dim3;

otraCaja = NULL;

}

double Caja::volumen(void) {

return (longitud * anchura * altura);

}

void Caja::point_at_next(Caja *where_to_point) {

otraCaja = where_to_point;

}

Caja *Caja::get_next(void) { // Este método devuelve la caja a la que apunta la caja actual

return otraCaja;

}

main() {

Caja pequeña(5, 4, 10), mediana (10, 6, 20), grande(20, 10, 30);

Caja *CajaPointer;

cout << 'El volumen de la Caja pequeña es ' << pequeña.volumen() << '\n';

cout << 'El volumen de la Caja mediana es ' << mediana.volumen() << '\n';

cout << 'El volumen de la Caja grande es' << grande.volumen() << '\n';

pequeña.point_at_next(&mediana);

mediana.point_at_next(&grande);

CajaPointer = &pequeña;

CajaPointer = CajaPointer->get_next();

cout << 'La Caja apuntada tiene volumen = ' << CajaPointer->volumen() << '\n';

}

El resultado de la ejecución será :

El volumen de la Caja pequeña es 200

El volumen de la Caja mediana es 1200

El volumen de la Caja grande es 6000

La Caja apuntada tiene volumen = 1200

En este programa:

· Hemos definido, en la parte privada de la clase Caja, un puntero a un objeto de la misma Clase. Esta es la estructura utilizada para la construcción de listas encadenadas.

· El constructor asigna el valor NULL al puntero. Esta es una buena idea, inicializar siempre los punteros. Haciendo esta asignación en el constructor, se garantiza que cada objeto de esta clase tendrá automáticamente su puntero inicializado.

· Hemos añadido dos funciones: point_at_next y get_next. Esta última devuelve un puntero a un objeto de la clase Caja.

· En el programa principal declaramos un puntero a un objeto del tipo Caja, CajaPointer.

· Hacemos que el puntero embebido en la caja pequeña apunte a la caja mediana, y que el puntero embebido en la caja mediana apunte a la caja grande. Hemos generado una lista encadenada con tres elementos.

· A continuación hacemos que el puntero CajaPointer apunte a la caja pequeña y lo utilizamos para referenciar a la caja pequeña y actualizarlo al valor contenido en la caja pequeña que es la dirección de la caja mediana. Hemos pasado, pues, de un elemento de la lista al siguiente llamando a una función de uno de los objetos.

Un objeto embebido en otro objeto. El puntero this.

En C++ se define this dentro de un objeto como un puntero al objeto en que está contenido. Se declara implícitamente como:

class_name *this;

y se inicializa para apuntar al objeto para el cual se llama a la función miembro. Este puntero es muy útil cuando se trabaja con punteros y especialmente en listas encadenadas cuando se necesita referenciar un puntero al objeto que se está insertando en la lista. La palabra this está disponible para este propósito y puede ser utilizada en cualquier objeto. Realmente la forma apropiada de referenciar a cualquier variable en una lista es a través del uso del puntero predefinido this, escribiendo this -> nombre_variable, pero el compilador supone que se está usando, y podemos omitir el puntero.

Funciones amigas.

Una función fuera de una clase puede definirse como función amiga por la clase que le da libre acceso a los miembros privados de la clase. Hay casos en que esto ayuda a hacer más legible un programa, y permite el acceso controlado a los datos.

Una función aislada puede ser declarada como amiga, así como miembro de otras clases, e incluso se le puede dar el status de amiga a clases enteras, si es necesario.

No pueden ser funciones amigas los constructores ni los destructores.

0 comentarios: