Clases en C++ Universidad de Carabobo Facultad Experimental de Ciencias y Tecnología Algoritmos y Programación II Enero de 2005 1. Definición de Clases Cada clase contiene : • • datos (datos miembro o campos), los cuales especifican las propiedades de los objetos. funciones (funciones miembro o métodos), mediante los cuales se modela su comportamiento y las acciones que puede realizar. Un ejemplo de esto se puede apreciar en el siguiente fragmento de código: 1 2 3 4 5 6 // Declaración de una clase class MiClase { // Declaración de datos miembro // Declaración de métodos }; En la declaración de una clase, para cada dato miembro, debe especificarse mediante los modificadores de acceso el ámbito desde el cual puede accederse a dicho miembro. Éstos son: • • • private: Sólo se permite su acceso desde los métodos de la clase. public: Se permite su acceso desde cualquier punto que pueda usar la clase. Un dato público es accesible desde cualquier objeto de la clase. protected: Se permite su uso en los métodos de la clase y en los de las clases derivadas mediante herencia. Un ejemplo de esto se puede apreciar en el siguiente fragmento de código: 1 2 3 4 5 class Triangulo { private: float cat_opuesto; float cat_adyacente; float hipotenusa; 6 7 8 9 2. // Declaración de métodos }; Constructores y Destructores Características de los constructores: • • • • • • Cuando se crea un objeto de una clase siempre se llama automáticamente a un constructor. Se emplea para iniciar los objetos de una clase. Es particularmente útil para reservar, si es necesario, memoria para ciertos campos del objeto. Pueden haber varios constructores para una clase. Un constructor tiene el mismo nombre que la clase en la que está declarado y no devuelve nada. OJO: NO es una función void. Cuando no se define ningún constructor en una clase, el compilador crea un constructor por defecto, sin argumentos, que inicia los datos miembros a cero. Características de los destructores: • • • • • Sólo hay un destructor para una clase. Cuando un objeto deja de existir siempre se llama automáticamente al destructor. Un destructor tiene el mismo nombre de la clase, precedido por el carácter “~”. El destructor no admite parámetros ni devuelve ningún valor. OJO: NO es una función void. Si no se especifica, el compilador proporciona un destructor por defecto. Su implementación tiene sentido sólo cuando el constructor ha reservado memoria dinámicamente. Un ejemplo de esto se puede apreciar en el siguiente fragmento de código: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class MiClase { public: int *arreglo; // Constructor MiClase () { arreglo = new int[10]; } // Destructor ~MiClase () { delete [] arreglo; } 15 16 17 18 19 3. }; Métodos Características de la declaración de los métodos: • • • • Se declaran como cua lquier función en C++. Se especifican los prototipos de los métodos en la declaración de la clase. Su implementación puede ser dentro de la declaración de la clase o en un archivo .cpp Es usual disponer de métodos públicos que modifican y/o leen el valor de los datos privados. Pueden definirse funciones privadas que pueden ser de utilidad (funciones auxiliares) para las funciones públicas. Como estilo de programación y organización, se recomienda tener un archivo para la declaración de la clase y otro para la implementación. Por ejemplo: triangulo.h (archivo que contiene la declaración de la clase Triangulo) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #ifndef triangulo_h #define triangulo_h class Triangulo { private: float cat_opuesto; float cat_adyacente; float hipotenusa; public: // Constructor de la clase Triangulo(); // Métodos de inspección float VerOpu(); float VerAdy(); float VerHip(); void MostrarTriangulo(); // Métodos de modificación void ModOpu(float valor); void ModAdy(float valor); void ModHip(float valor); void ModificarTriangulo(float ady, float op, float hip); 26 27 28 29 }; #endif triangulo.cpp (archivo que contiene la implementación de la clase Triangulo) 1 2 3 4 5 6 #include <iostream> #include “triangulo.h” using namespace std; // Constructor de la clase Triangulo::Triangulo() 8 { 9 this->cat_opuesto = 0; 10 cat_adyacente = 0; 11 hipotenusa = 0; 12 } 7 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 // Métodos de inspección float Triangulo::VerOpu() { return cat_opuesto; } float Triangulo::VerAdy() { return cat_adyacente; } float Triangulo::VerHip() { return hipotenusa; } void Triangulo::MostrarTriangulo() { cout << "Cateto Opuesto: " << VerOpu() << endl; cout << "Cateto Adyacente: " << VerAdy() << endl; cout << "Hipotenusa: " << VerHip() << endl; } // Métodos de modificación void Triangulo::ModOpu(float valor) { cat_opuesto = valor; } void Triangulo::ModAdy(float valor) 44 { cat_adyacente = valor; 45 46 47 } 48 void Triangulo::ModHip(float valor) { hipotenusa = valor; } 49 50 51 52 53 54 55 56 57 58 59 void Triangulo::ModificarTriangulo(float ady, float op, float hip) { ModOpu(op); ModAdy(ady); ModHip(hip); } NOTA: El apuntador this es una variable predefinida en todas las funciones o métodos miembro de una clase. Contiene la dirección del objeto concreto de la clase sobre la cual se está aplicando la función u operador miembro. Al ser apuntador, el acceso a los miembros del objeto se realizará con el operador flecha (->). 4. Instanciación Un objeto se puede crear de dos formas: • Estática (el objeto existe mientras no se pierda alcance sobre él). Sinaxis: • Nombre_de_clase variable_del_objeto; Dinámica (el objeto se crea en memoria dinámica, y existe mientras no sea eliminado explícitamente de ésta). Sintaxis: Nombre_de_clase *apuntador_al_objeto; apuntador_al_objeto = new Nombre_de_clase; Un ejemplo de esto se puede apreciar en el siguiente fragmento de código: 1 2 3 4 5 6 7 8 9 void main () { // Instanciación estática Triangulo a; // Instanciación dinámica Triangulo *b; b = new Triangulo; ... 10 11 delete b; 12 13 5. } Paso de Mensajes Es la forma de comunicación entre dos o más objetos (instancias), de clases similares o diferentes, por medio de operaciones. Un ejemplo de esto se puede apreciar en el siguiente fragmento de código: 1 2 #include <iostream> 3 class A { private: ... 4 5 6 7 public: int Op1() { ... } 8 9 10 11 }; 12 class B { public: int x; 13 14 15 16 int Op2(A obj) { return (x + obj.Op1()); } 17 18 19 }; 20 void main () { A obj1; B obj2; 21 22 23 24 25 26 ... 27 28 29 obj2.x = 100; // Paso de mensaje cout << obj2.Op2(obj1); 30 31 } Observe el siguiente ejemplo que extiende a la clase Triangulo: 1 2 3 4 5 #include <iostream> #include “triangulo.h” Triangulo::Copiar(Triangulo t) { cat_opuesto = t.VerOpu(); cat_adyacente = t.VerAdy(); hipotenusa = t.VerHip(); 6 7 8 9 10 11 12 13 14 } void main () { Triangulo t1, t2; cout << “Triangulo t1” << endl; t1.MostrarTriangulo(); t1.ModOpu(3.3); t1.ModAdy(5.0); t1.ModHip(7.0); cout << “Triangulo t1 modificado” << endl; t1.MostrarTriangulo(); t2.ModificarTriangulo(1.0, 1.0, 1.0); 15 16 17 18 19 20 21 22 23 24 // Paso de mensaje t1.Copiar(t2); 25 26 cout << “Triangulo t1 copiado de t2” << endl; t1.MostrarTriangulo(); 27 28 29 6. } Herencia La herencia es una forma de reutilización del software, en la cual se crean clases nuevas a partir de clases existentes, mediante la absorción de sus atributos y comportamientos, y enriqueciendo éstos con las capacidades que las clases nuevas requieren. Las clases nuevas se denominan clases derivadas, en donde cada clase derivada se convierte en candidata a clase base para alguna clase futura. Existen tres tipos de herencia: pública, privada y protegida. Sintaxis class nombre_clase_derivada : <tipo_herencia> nombre_clase_base { ... }; Un ejemplo de esto se puede apreciar en el siguiente fragmento de código: 1 2 3 4 5 6 class Isosceles : public Triangulo { private: int color; public: // Constructor de la clase Isosceles(float catetos, float hip) { cat_opuesto = catetos; cat_adyacente = catetos; hipotenusa = hip; } 7 8 9 10 11 12 13 14 15 // Métodos de inspección float VerCatetos() { return cat_opuesto; } int VerColor() { return color; } 16 17 18 19 20 21 22 23 24 25 // Métodos de modificación void ModCatetos(float valor) { cat_opuesto = valor; cat_adyacente = valor; } void ModColor(int valor) { color = valor; } 26 27 28 29 30 31 32 33 34 35 }; NOTA: Si la clase base posee un único constructor que recibe unos parámetros, entonces el encabezado del constructor de la clase derivada debe invocar al constructor de la clase base con los parámetros requeridos. Un ejemplo de esto se puede apreciar en el siguiente fragmento de código: 1 2 3 4 5 class A { private: ... public: A(int x, int y) { ... } ... 6 7 8 9 10 11 12 13 }; class B : public A { private: int z; 14 15 public: B(int x, int y, int z) : A(x,y) { this->z = z; } ... 16 17 18 19 20 21 22 };