Daniel Garcia

sábado, julio 17, 2010

ESTRUCTURAS EN LENGUAJE C

Las estructuras ya estaban presentes en C. Hay quien las ve como una clase, pero sin métodos (sólo almacena datos).

Supongamos que queremos hacer una agenda con los números de teléfono de nuestros amigos. Necesitaríamos un array de Cadenas para almacenar sus nombres, otro para sus apellidos y otro para sus números de teléfono. Esto puede hacer que el programa quede desordenado y difícil de seguir. Y aquí es donde vienen en nuestro auxilio las estructuras.

Para definir una estructura usamos el siguiente formato:

struct nombre_de_la_estructura {
campos de estructura;
};



NOTA: Es importante no olvidar el ';' del final.



Vamos a crear una declaración de estructura llamada amigo:



struct estructura_amigo {
char nombre[30];
char apellido[40];
char telefono[10];
char edad;
};



A cada elemento de esta estructura (nombre, apellido, teléfono) se le llama campo o miembro.



Una vez definida la estructura, podemos usarla declarando una variable con esa estructura:



struct estructura_amigo amigo;



Ahora la variable amigo es de tipo estructura_amigo. Para acceder al nombre de amigo usamos: amigo.nombre.



Arrays de estructuras



Supongamos ahora que queremos guardar la información de varios amigos. Con una variable de estructura sólo podemos guardar los datos de uno. Necesitamos declarar arrays de estructuras:



struct estructura_amigo amigo[10];



Ahora necesitamos saber cómo acceder a cada elemento del array. La variable definida es amigo, por lo tanto para acceder al primer elemento usaremos amigo[0] y a su miembro nombre: amigo[0].nombre.



Inicializar una estructura



Primero se define la estructura y luego al declarar una variable como estructura le damos el valor inicial que queramos. Ejemplo:



struct estructura_amigo amigo = {
'Juanjo',
'Lopez',
'592-0483',
30
};



Por supuesto hemos de meter en cada campo el tipo de datos correcto.



Punteros a estructuras



Primero hay que definir la estructura igual que antes, pero al declarar la variable de tipo estructura debemos ponerle el operador '*' para indicarle que es un puntero.



Es importante recordar que un puntero no debe apuntar a un lugar cualquiera, debemos darle una dirección válida donde apuntar. No podemos por ejemplo crear un puntero a estructura y meter los datos directamente mediante ese puntero, no sabemos dónde apunta el puntero y los datos se almacenarían en un lugar cualquiera.



Y para comprender cómo funcionan nada mejor que un ejemplo. Este programa utiliza un puntero para acceder a la información de la estructura:



#include <stdio.h>
struct estructura_amigo {
char nombre[30];
char apellido[40];
char telefono[10];
int edad;
};
struct estructura_amigo amigo = {
'Juanjo',
'Lopez',
'592-0483',
30
};
struct estructura_amigo *p_amigo;
int main()
{
p_amigo = &amigo;
printf( '%s tiene ', p_amigo->apellido );
printf( '%i años ', p_amigo->edad );
printf( 'y su teléfono es el %s.\n' , p_amigo->telefono );
}


p_amigo es un puntero a la estructura estructura_amigo. Dado que es un puntero tenemos que indicarle dónde debe apuntar, en este caso vamos a hacer que apunte a la variable amigo:



 p_amigo = &amigo;


El operador & que significa 'dame la dirección donde está almacenado...'.



Para acceder a cada campo de la estructura antes lo hacíamos usando el operador '.', pero, como muestra el ejemplo, si se trabaja con punteros se debe usar el operador '->'.




Punteros a arrays de estructuras



Por supuesto también podemos usar punteros con arrays de estructuras.



Paso de estructuras a funciones



Las estructuras se pueden pasar directamente a una función igual que hacíamos con las variables. Ejemplo:



int suma( struct estructura_amigo arg_amigo ) {



return arg_amigo.edad+20;



}



CLASES



Terminología.



· Una clase es un grupo de datos y métodos (funciones). Es sólo un patrón que será usado para crear una variable que pueda ser manipulada en el programa.



· Un objeto es un ejemplo de una clase, lo que es similar a decir que una variable que hemos definido como un ejemplo de un tipo. Un objeto es lo que realmente se utiliza en un programa, ya que tiene valores que pueden ser cambiados.



· Un método es una función contenida en una clase.



· Un mensaje es lo mismo que una llamada a una función. En programación orientada a objetos se envían mensajes en lugar de llamar a funciones. En principio se puede pensar en ambas cosas como equivalentes




Clases.



El principal objetivo en un lenguaje orientado a objeto es que se pueden crear objetos que contienen los datos y los métodos para manipularlos. Además, el programador puede decidir si los datos y los métodos son visibles o no al resto del programa. En C++, estos objetos se implementan mediante clases:



class Caja {



// Datos y Métodos



};




La declaración de clase está encerrada entre { y }, como cualquier otro bloque en C++. Es importante el punto y coma que sigue a }. Utilizaremos para los nombres de las clases una letra mayúscula como primer carácter, y las demás minúsculas.



Veamos una clase muy simple, que contiene sólo datos. Hagámoslos públicos, para que puedan ser accedidos desde el programa:



# include <iostream.h>



class Caja {



public:



double longitud, anchura, altura;



};



main () {



Caja pequeña, mediana;



class Caja grande;



pequeña.longitud = 5;



mediana.longitud = 10;



grande.longitud = 20;



pequeña.anchura = 4;



mediana.anchura = 6;



grande.anchura = 10;



pequeña.altura = 10;



mediana.altura = 20;



grande.altura = 30;



cout << ' La longitud de la caja grande es ' << grande.longitud <<'\n';



cout << ' La anchura de la caja pequeña es ' << pequeña.anchura <<'\n';



cout << ' La altura de la caja mediana es ' << mediana.longitud <<'\n';



}




Vemos que:



· En primer lugar, definimos una clase llamada Caja, formada por tres variables: longitud, anchura y altura.



· En la función principal, declaramos tres variables de este tipo (tres objetos), pequeña, mediana y grande, para lo cual podemos utilizar o no la palabra class antes del nombre de la clase. Cada una de estas cajas contiene tres números reales, sus dimensiones.



· La palabra public en la definición de la clase es en este caso necesaria porque las variables en una clase son por defecto privadas, y no se puede acceder a ellas directamente desde el programa principal. En este caso, las tres variables reales están disponibles desde cualquier parte del programa principal. Cada variable puede ser inicializada, incrementada, leída, modificada, o sufrir cualquier operación que definamos sobre ella. Como ejemplo, en el programa hemos inicializado las dimensiones de cada objeto y a continuación las hemos imprimido.



· Los datos miembro de una clase son accedidos como los de una estructura de C: el nombre de la variable estructura/clase seguido por el dato miembro, separados ambos por un '.'




 



clases y encapsulacion en C



Encapsulación.



Los métodos son una interface a través de la cual se manipulan los datos almacenados en una clase. Usando estos métodos, podemos manipular y extraer datos de un objeto Caja sin saber qué tipos de datos se usan para almacenar los datos. Esto se conoce como encapsulación de datos, y es un concepto muy importante en la programación orientada a objetos. Encapsulación es la habilidad de una parte de un programa para ocultar sus datos al resto del código, impidiendo así accesos incorrectos o conflictos con los nombres de otras variables.



En el ejemplo anterior, cada clase tenía sólo una sección, etiquetada como public. Pero podemos utilizar también la etiqueta private. Así, la palabra public hace que los campos (variables o funciones) que le siguen en la clase puedan ser accedidas desde cualquier parte del programa, mientras que la palabra private indica que los campos que le siguen sólo son accesibles por el código que forma parte de la misma clase.



Si no se indica lo contrario, por defecto en una clase todos los elementos son privados. Veamos un ejemplo:



# include <iostream.h>



class Caja {



double longitud, anchura, altura;



public:



void set (double dim1, double dim2, double dim3);



void print (void);



double getLongitud (void);



};



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



longitud = dim1;



anchura = dim2;



altura = dim3;



};



void Caja :: print (void) {



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



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



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



};



double Caja:: getLongitud (void) {



return longitud;



};



main () {



double longitud;



Caja pequeña, mediana, grande;



pequeña.set(5, 4, 10);



mediana.set (10, 6, 20);



grande.set (20, 10, 30);



pequeña.print();



mediana.print();



grande.print();



longitud = grande.getLongitud();



cout << 'longitud de la caja grande = ' << longitud <<'\n';



}




La ventaja de las funciones miembro es que la función llamada puede automáticamente acceder a los datos del objeto para el cual fue llamado. Así, en grande.print(), el objeto grande es el 'substrato': las variables longitud, altura y anchura que son utilizadas en el código de la función se refieren al objeto grande.



La sección privada de una clase es la parte de los datos que no puede ser accedida desde fuera de la clase, está escondida para cualquier acceso externo. Las variables longitud, anchura y altura del ejemplo, que son parte del objeto grande, no están disponibles para su uso en cualquier lugar del programa principal.



Se introduce la palabra public para indicar que puede accederse a todo lo que le sigue desde fuera de esta clase. En este caso, hemos declarado tres funciones públicas, que pueden ser llamadas desde la función main. Por supuesto, estas funciones, llamadas funciones miembro, tienen acceso a la parte privada de la clase. Por tanto, desde el programa principal sólo se puede acceder a las dimensiones de una caja llamando a las estas funciones.



Una vez declaradas las funciones dentro de la clase, debemos definirlas para establecer qué acción realizan. Esto se hace de la forma habitual, salvo que el nombre de la clase se antepone, seguido del operador ::, al nombre de la función. La definición de una función se denomina la implementación de la función. Es necesario especificar el nombre de la clase porque se puede utilizar el mismo nombre para funciones de otras clases, y el compilador debe saber a qué clase corresponde cada implementación.



Los datos privados de la clase son accesibles para las funciones miembro, y en la implementación de éstas pueden ser modificados y leídos en la forma habitual. Esto se puede hacer con los datos privados que forman parte de la clase a la que pertenece la función, pero los datos privados de otras clases están escondidos y no se puede acceder a ellos a través de funciones miembro de esta clase. Esta es la razón por la que se antepone el nombre de la clase al nombre de la función cuando se define ésta.



En la parte privada de una clase se pueden incluir variables y funciones, y otras variables y funciones en la parte públicas. En la mayor parte de las situaciones prácticas, las variables se incluyen en la parte privada, y las funciones sólo en la parte pública de la clase.



En C++ tenemos tres ámbitos de validez para las variables: local, file y clase. Las variables locales están localizadas en una única función, y las variables de fichero se pueden utilizar en cualquier lugar del fichero que sigue a su definición. Una variable cuyo ámbito es una clase se puede utilizar en el ámbito de utilización de la clase y en ningún otro sitio.



Más funciones.



La idea básica de la programación orientada a objetos es definir tipos de dato abstractos, y las operaciones o métodos que pueden actuar sobre ellos. Añadamos al ejemplo anterior una función que calcule el volumen de un objeto de la clase Caja:



# include <iostream.h>



class Caja {



double longitud, anchura, altura;



public:



void set (double dim1, double dim2, double dim3);



double volumen (void);



};



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



longitud = dim1;



anchura = dim2;



altura = dim3;



};



double Caja:: volumen (void) {



return longitud * anchura * altura;



};



main () {



double longitud; Caja pequeña, mediana, grande;



pequeña.set(5, 4, 10);



mediana.set (10, 6, 20);



grande.set (20, 10, 30);



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



}




Esto impide que al calcular el volumen de una caja mezclemos las dimensiones de dos objetos distintos.



CLASES CONSTRUCTORES Y DESCTRUCTORES EN C



Constructores.



Cuando creamos un objeto de una clase, siempre seguimos los mismos pasos para llamar a un conjunto de métodos que inicializen los datos del objeto. En C++, se define una función especial, el constructor, que es llamada cuando se crea un nuevo objeto de la clase. Este constructor puede recibir parámetros, como cualquier otra función. Veamos un ejemplo de clase sin constructor y con constructor:



# include <iostream.h>



class Caja {



double longitud, anchura, altura;



public:



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



double volumen (void);



};



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



longitud = dim1;



anchura = dim2;



altura = dim3;



};



double Caja:: volumen (void) {



return longitud * anchura * altura;



};



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';



}




En este sencillo ejemplo vemos las características más importantes de un constructor:



· El constructor tiene el mismo nombre que la clase a la que pertenece. Por ejemplo, el constructor de la clase Caja se llama Caja.



· El constructor no devuelve nada (ni siquiera void).



· Los argumentos se pasan al constructor en la declaración de un objeto. Así, Caja pequeña(5, 4, 10) crea un nuevo objeto de la clase Caja y llama al constructor con los parámetros 5, 4 y 10.




Cuando se crea un objeto de una clase, se llama automáticamente al constructor. Si tuviésemos un puntero a una clase, su constructor no sería llamado a menos que reserváramos memoria para lo que apunta el puntero, utilizando new.



Un constructor es una función, y por tanto podemos aplicarle todo lo que hemos visto que las funciones en C++ pueden hacer. Por ejemplo, podemos sobrecargarlo:



# include <iostream.h>



class Caja {



double longitud, anchura, altura;



public:



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



Caja (void);



double volumen (void);



};



Caja :: Caja (void) {



longitud = 8;



anchura = 8;



altura = 8;



};




Hemos creado una función constructora que inicializa las dimensiones de la caja a 8. Esta será llamada cuando creemos un objeto del tipo Caja sin ningún parámetro.



Destructores.



El destructor es muy similar al constructor, excepto que es llamado automáticamente cuando cada objeto sale de su ámbito de validez. Recordemos que las variables automáticas tienen un tiempo de vida limitado, ya que dejan de existir cuando se sale del bloque en que han sido declaradas. Cuando un objeto es liberado automáticamente, su destructor, si existe, es llamado automáticamente.



Un destructor tiene el mismo nombre que la clase a la que pertenece, pero precedido con una tilde (~). Igual que el constructor, un destructor no devuelve nada.



Si algún bloque de memoria fue reservado dinámicamente en un objeto, se puede utilizar el destructor para liberarlo antes de que se pierdan los punteros a esas variables.



#include <iostream.h>



class Taco {



public:



Taco (int hard) {



dureza = new int;



*dureza = hard;



}



~Taco() {



cout << 'Destruyendo taco con dureza ' ;



cout << *dureza <<;\n';



delete dureza;



}



private:



int *dureza;



};



main () {



Taco hard(10);



Taco *soft = new Taco (0);



delete soft;



};




En este ejemplo, vemos que el destructor tiene el mismo nombre que la clase, con un ~ delante. Cuando se crean punteros a clases, como soft en el ejemplo, se llama al destructor cuando se libera la memoria del puntero. Si esto no se hace, nunca se llamará al destructor.



Con clases declaradas estáticamente, como Taco hard, el destructor se llama al final de la función donde se declara el objeto (en el ejemplo, al final de la función main.



Incluso cuando se interrumpe un programa usando una llamada a exit(), se llama a los destructores de los objetos que existen en ese momento.

0 comentarios: