Clases

Programación en C++. Computación. Recursos. Objetos. Autorreferencia. Constructores. Clases amigas. Implementación

1 downloads 353 Views 421KB Size

Story Transcript

CLASES Introducción • Permite crear objetos que pueden ser empleados del mismo modo que los tipos básicos • Permite definir un objeto de forma concreta y precisa que no tiene contraparte obvia con los tipos básicos • Permite diseñar los objetos de una aplicación de acuerdo a lo que se está desarrollando con lo que se puede entender y modificar de mejor manera la aplicación Ejemplo : Implementación del concepto de fecha por medio de una estructura (struct) junto con el conjunto de funciones para manipularlas : En C : struct fecha { int dÃ−a, mes, año }; fecha hoy ; void fijar_fecha (fecha*, int, int, int); void siguiente_fecha (fecha*); void imprimir_fecha(const fecha*); // ... No existen conexiones explÃ−citas entre las funciones y el tipo de datos. C++ nos permite hacer lo siguiente : struct fecha { int dÃ−a, mes, año; void fijar(int, int, int); void obtener(int*, int*, int*); void siguiente(); void imprimir(); }; Las funciones miembros se invocan de la siguiente manera : fecha hoy; fecha mi_aniversario; 1

void f () { mi_aniversario.fijar(30, 12,1975); hoy.fijar(22,9,1997); mi_aniversario.imprmir(); hoy.siguiente(); } Diferentes estructuras pueden tener funciones miembro con el mismo nombre, es preciso especificar el nombre de la estructura al definir una función miembro : void fecha::siguiente() { if(++dÃ−a > 28) { // resolver esta parte } } En una función miembro, los nombres de los miembros se pueden emplear sin hacer una referencia explÃ−cita a un objeto, en ese caso se refieren al miembro del objeto que efectivamente llamó a la función. DEFINICIà N DE CLASES La definición de tipo struct no poseen miembros privados y no restringen el acceso de otra funciones a los objetos. La definición de una clase nos permite implementar esta posibilidad . class fecha { int dÃ−a, mes, año; public : void fijar(int, int, int); void obtener(int*, int*, int*); void siguiente(); void imprimir(); }; 2

La palabra reservada public nos separa la definición de la clase en dos partes. Sólo las funciones miembro pueden acceder a los nombres definidos en la primera parte, que se denomina privada. La parte pública consiste es la interfase de los objetos de la clase, esto le permite “comunicarse” con el resto de los componentes de un programa. Las funciones que no son miembros no podrán utilizar los miembros privados de la clase fecha. Ventajas de limitar el acceso : • cualquier problema que exista con una fecha que tome un valor ilegal habrá que buscarlo en el código de alguna función miembro • un cambio de comportamiento de un objeto habrá que efectuarlo a través de las funciones miembro • examinando la definición de la funciones miembros un usuario podrá aprender su manejo AUTOREFERENCIA Una función miembro puede hacer referencia directa a miembros del objeto al cual invoca. class X { int m; public : int leerm() { return m }; }; void f( X aa, X bb) { int a = aa.leerm(); int b = bb.leerm(); //..... } El puntero this se define de la siguiente forma : X * const this; this se inicializa de modo que siempre tenga la dirección del objeto al cual se llamó a la función miembro. This tiene una posición constante, varÃ−a el objeto al que apunta. class X { int m; 3

public : int leerm() { return this->m }; }; La utilización de this puede ser útil en la implementación de listas doblemente vinculada : class double_link_list { double_link_list* prev; // puntero al nodo anterior double_link_list* next; // puntero al siguiente nodo public : void agrega(double_link_list*); }; void double_link_list*:: anexar(double_link_list* p) { p->next = next; // o sea p-> next = this-> next p->prev = this; // uso explÃ−cito de this next->pre = p; // o sea this->next->pre = p next = p; // this->next = p } double_link_list* cabeza_lista; void f(double_link_list* a, double_link_list* b) { //... cabeza_lista->agregar(a); cabeza_lista->agregar(b); } Inicialización Constructores : funciones que se emplean para inicializar objetos de una clase.

4

Si no se declara existe un por omisión (default). Tienen el mismo nombre de la clase. class fecha { // ... fecha(int, int, int); }; Si el constructor tiene argumentos habrás que pasarselos : fecha hoy = fecha(22, 9, 1997); fecha navidad(25,12,0) // forma abreviada de llamar al // constructor fecha mi_aniversario; // error se deben pasar los // argumentos Se pueden declarar varios constructores empleando la sobrecarga de nombre de funciones : class fecha { int dÃ−a, mes, año; public : // .... fecha(int, int, int); // dÃ−a, mes, año fecha(int, int); // dÃ−a, mes; año actual fecha(int); // dÃ−a; mes y año actuales fecha(); // fecha por omisión : hoy fecha (const char*) // fecha como cadena de }; // caracteres Cuando los constructores difieren lo suficiente en cuanto a sus tipos de argumentos, el compilador selecciona el correcto : fecha hoy(4); fecha primavera(“21 de setiembre”); fecha alguien(5,11); fecha ahora; // inicializado por omisión 5

Hay que cuidar de poner los constructores que sean necesarios realmente para no producir un programa poco legible. Utilizar constructores por omisión puede ser muy importante. class fecha { int dÃ−a, mes ,año; public: // ... fecha(int d=0; int m=0; int y=0); // ... }; fecha::fecha(int d, int m, int a) { dÃ−a = d ? d : hoy.dÃ−a; mes = m ? m : hoy.mes; año = a ? a : hoy.año; // verificar que la fecha sea válida // ... } Un objeto de una clase sin constructores se puede inicializar asignándole otro objeto de esa clase. Esto se puede hacer aún cuando se hayan declarado constructores. Por ejemplo: fecha f = hoy; // inicialización por asignación Constructor por omisión: copia de otro objeto de la misma clase. La copia se efectúa miembro por miembro. CARACTERà STICAS Y REGLAS DE LOS CONSTRUCTORES • El nombre del constructor debe ser el mismo que el de su clase • No debe tener ningun tipo de retorno ni siquiera void • Una clase puede tener distintos tipos de constructores o ninguno, el compilador asigna uno automáticamente a esa clase • Un constructor predeterminado es aquel que no tiene ningún tipo de parámetro o posee una lista de parámetros donde todos ellos son predeterminados // constructor sin parámetros

6

class punto { double x; double y; public: punto(); …… }; // la misma clase con constructor con argumentos // predeterminados class punto { double x; double y; public: punto(double xval=0, double yval=0); …… }; • El constructor de copia permite crear una instancia de clase usando una instancia existente class punto { double x; double y; public: punto(); punto(double xval, double yval); punto(const punto& pt); …… };

7

Ejemplo de construcciones : punto p1; // llama al constructor predeterminado punto p2(1.1, 1.3); // llama al constructor con dos // argumentos punto p3(p2); Construcción y destrucción Los objetos se pueden crear como : • Objetos automáticos : se crean cuando se llega a su declaración durante la ejecución de un programa, se destruyen cuando se sale del bloque en que aparecen • Objetos estáticos : se crean al principio del programa y se destruyen cuando se sale del mismo • Objetos dinámicos : se crean a partir del almacenamiento disponible, se crean con new se eliminan con delete • Objetos miembro : se crean como miembros de otra clase o como elemento de un arreglo Variables locales : • constructor se ejecuta cuando se pasa por la declaración de la variable • destructor se ejecuta cuando se sale del bloque de esa variable Los destructores se ejecutan en el orden inverso al de su construcción void f(int i) Construcción Destrucción { tabla aa; tabla bb; if(i>0) { tabla cc; //… } //… } void h() 8

{ tabla t1(100); // constructor tabla t2 = t1; // posibles problemas tabla t3(200); // constructor t3 = t2; // problemas } // En este ejemplo el constructor se llama 2 veces y el // destructor 3 veces Almacenamiento disponible main() { tabla *p= new tabla(100); tabla *q= new tabla(200); delete p; delete p; // posible error de ejecución }// nunca se eliminó a q y se destruyó a p dos veces No existen garantÃ−as que un destructor llamado con new se llame a su destructor alguna vez No eliminar no significa nada excepto desperdicio de espacios Arreglo de objetos de clase Para declarar un arreglo de objetos de una clase con un constructor debe tener un constructor por omisión tal que pueda llamarse con una lista de argumentos vacÃ−a tabla tab[10]; Cuando se destruye se debe llamar al constructor para cada elemento de un arreglo cuando este se destruye void f() { tabla* t1= new tabla; tabla* t2= new tabla[10]; delete t1; // una tabla no hay problemas 9

delete t2; // problemas 10 tablas delete[] t2; } CLASES AMIGAS En algunos casos es necesario que una función que no es miembro de una clase pueda acceder a los miembros privados de la clase en estos casos se denomina a la función amiga (friend). vector multiplicar(const matriz& m, const vector &v) { vector r; for(int i=0; i<3; i++) {// r[i]=m[i]*v; r.elem(i)=0; for (int j=0; j<3; j++) r.elem(i)+=m.elem(i,j)*v.elem(j); } return r } class matriz; class vector { float v[4]; //… friend vector multiplicar(const matriz&,const vector&); }; class matriz { vector v[4]; //… friend vector multiplicar(const matriz&,const vector&); }; La función friend no tiene nada particular excepto el derecho de acceder a la parte privada de una clase. 10

Una función friend se puede declarar tanto en la parte privada como en la pública de una clase. La función amiga se declara explÃ−citamente en la clase en la cual es amiga â ´ forma parte de la interfase de esa clase tanto como una función miembro. Se debe tener en cuenta que en la función friend no se puede emplear el puntero this, por lo tanto se debe referenciar explÃ−citamente el miembro del objeto con el que se está trabajando. vector multiplicar(const matriz& m, const vector &v) { vector(int i=0; i<3; i++) { r.v[i]=0; for (int j=0; j<3; j++) r.v[i]+=m.v[i][j]*v.v[j]; } return r } Una función miembro de una clase puede ser amiga de otra class X { //… void f(); }; class Y { //… friend void X::f();// la función miembro f de X es // amiga de Y }; Para declarar que todas las funciones miembros de una clase son amigas de otras tenemos : class X { … friend class Y; // todas las funciones miembros de Y 11

… // son amigas de X }; Distinción de Nombres de Miembros El operador determinación de alcance :: sirve para distinguir los nombres de los miembros de una clase y otros nombres. class X{ int m; public : int leem() const{return m;} void fijam (int m) {X::m=m;} }; El argumento m oculta al miembro m nos referimos a él con el nombre calificado X::m. Si se emplea un nombre precedido por el operador :: nos referimos a un nombre global. class mi_archivo { //… public: int open(const char*, const char*) }; int mi_archivo::open(const char*nombre, const char*espec) { //… if(::open(nombre, bandera)) { open del S.O. //… } } Limpieza Está relacionado con los destructores, estos garantizan la liberación de memoria al desechar objetos del 12

tipo. Destructor de la clase X : ~X (complemento del destructor) class pila_caract { int tam; char* superior; char* s; public: pila_caract(int tm) {superior=s=new char[tam=tm];} ~pila-caract() {delete s;} //destructor void meter(char c) {*superior++ = c;} char sacar() {return *--superior} }; Clases anidadas class conjunto { struct miembro_conj{ int miembro; miembro_conj* siguiente; miembro_conj(int m, miembro_conj* n) { miembro=m; siguiente=n; } }; miembro-conj* primero; public: conjunto() { primero=0; } insertar(int m) { primero=new miembro_conj(m,primero);} };

13

Una clase anidada queda oculta dentro de una clase Miembro_conj m1(,0) //error!! miembro_conj no está en el // alcance global Este recurso no es útil cuando la clase anidada no es simple Otra alternativa es : class miembro_conj{ friend class conjunto;// solo tienen acceso los miembros // de conjunto int miembro; miembro_conj* siguiente; miembro_conj(int m, miembro_conj* n) {miembro=m, siguiente=n} // muchos otros miembros útiles }; class conjunto { miembro_conj * primero; public: conjunto() {primero=0;} insertar(int m) { primero=new miembro_conj(m,primero); } }; Es posible acceder al nombre de una clase miembro desde afuera de la clase que encierra. Por ejemplo : class X{ struct M1{int m;}; public:

14

struct M2 {int m;}; M1 f(M2); }; void f() { M1 a; // error!! M1 no está en el alcance M2 b; //error!! M2 no está en el alcance X::M1 a; // error!! X::M1 es privado X::M2 d; // correcto } Otros modos de hacerlo : M1 X::f(M2 a) // error el nombre M1 está fuera del alcance X::M1 X::f(M2 a) // correcto X::M1 X::f(X::M2 a)// correcto pero sobra el tercer X:: Miembros estáticos Una clase es un tipo, no un objeto de datos, cada objeto de datos tiene su propia copia de los miembros de datos de la clase. Por medio de la declaración static un miembro de la clase tendrá un sola copia y no una por cada objeto. class tarea { // … static tarea* cadena_tareas; static void planificar(int); // … }; static es solo una declaración el objeto debe estar definido más adelante, en algún lugar del programa : tarea* tarea::cadena_tareas = 0; void tarea::planificar(int p) { /* … */}

15

La palabra static no es necesaria ni está permitida en la definición de un miembro estático Punteros a miembros Para obtener la dirección de un miembro de una clase : &nombre_clase::nombre_miembro Para tener un puntero a un miembro de una clase tenemos : X::* #include struct cl { char *val void imprimir(int x) { cout << val<*pf)(4); } EJEMPLO DE UNA IMPLEMENTACIà N

16

Supongamos una tabla de sÃ−mbolos de un programa (generalmente compuesta por los nombres de variables), supongamos una primera forma como estructura : struct nombre { char* cadena; nombre* sig; double valor; }; y ahora implementada como clase : // archivo tabla.h class tabla { nombre* tab; public: tabla() { tab = 0; } nombre* buscar{char*, int = 0); nombre* insertar(char*s) { return buscar(s,1); } }; ahora estamos en condiciones de declarar más de una tabla, tener un apuntador a una tabla. Por ejemplo : #include “tabla.h” tabla globales; tabla palabra_clave; tabla* locales; main() { locales = new tabla; //... } Y ahora vamos a ver una implementación de tabla::buscar() que emplea una búsqueda lineal a través de 17

una lista vinculada de nombres de la tabla : #include nombre* tabla::buscar(char* p, int ins) { for (nombre* n=tab; n; n=n->siguiente) if(strcmp(p,n->cadena) == 0) return n; if(ins == 0) error (“no se encuentra el nombre”); nombre* nn = new nombre; nn->cadena = new char(strlen(p)+1); strcpy(nn->cadena, p); nn->valor = 1; nn->siguiente = tab; tab = nn; return nn; } Supongamos ahora que queremos mejorar la clase tabla para utilizar una búsqueda por dispersión : clase tabla { nombre** tab; int tam; public: tabla(int tam = 15); ~tabla(); nombre* buscar (char*, int = 0); nombre* insertar(char *s) { return buscar(s,1); } }; La estructura de datos y el constructor fueron modificados para reflejar las nuevas necesidades. La inclusión de un tamaño especÃ−fico en el constructor es porque la búsqueda por dispersión necesita tener un 18

tamaño. La asignación de un tamaño por omisión es necesaria para que el código anterior siga siendo válido. tabla::tabla(int tm) { if(tm < 0) error(“tabla de tamaño negativo”); tab = new nombre*[tam=tm]; for (int i=0; isiguiente; delete n->cadena; delete n; } } delete tab; } Ahora especificamos la función buscar : nombre* tabla::buscar(const char* p, int ins) { int ii = 0; char* pp = p; while (*pp) ii = ii<< 1^*pp++; if( ii<0 ) ii = -ii; 19

ii %=tam; for (nombre* n=tab[ii]; n; n= n->siguiente) if(strcmp(p,n->cadena) == 0) return n; if (ins == 0) error(“no se encuentra el nombre”); nombre* nn= new nombre; nn->cadena = new char{strlen(p)+1}; strcpy(nn->cadena,p); nn->valor = 1; nn->siguiente = tab[ii]; tab [ii] = nn; return nn; } Objetos de clase como miembros class tabla{ nombre ** tab; int tam; public: tabla(int tm=15); ~tabla(); nombre* buscar(char*, int=0); nombre* insertar(char* s) { return buscar(s,1); } }; class defclase{ tabla miembros; int num_miembros; .... public: defclase(int tamaño);

20

~defclase(); }; Defclase debe contener una tabla de miembros de tamaño tamaño. Como se llama al constructor de tablas? defclase::defclase(int tamaño): miembros(tamaño) { num_miembros= tamaño; } El constructor de miembros se llama antes del cuerpo del constructor especificando su lista de parámetros. Si tenemos más miembros como en el siguiente caso: class defclase{ tabla miembros; tabla amigas; int num_miembros; .... public: defclase(int tamaño); ~defclase(); }; defclase::defclase(int tamaño): miembros(tamaño), amigas(tamaño), num_miembros (tamaño) { ... } Si un constructor de un miembro no necesita argumentos no es necesario especificar el constructor de miembros en la lista de argumentos, por ejemplo: defclase::defclase(int tamaño): miembros(tamaño),

21

num_miembros (tamaño) { ... } AquÃ− se llama al constructor por omisión (sin argumentos) por lo tanto el miembro amigas tendrá un tamaño de 15 Universidad Tecnológica Nacional - Santa Fe - Departamento Sistemas Curso : Desarrollos de Programación en C++

22

Get in touch

Social

© Copyright 2013 - 2024 MYDOKUMENT.COM - All rights reserved.