Sei sulla pagina 1di 39

CARACTERSTICAS DE C++ Son varias, las caractersticas que C++ incorpor como natural evolucin del lenguaje C.

Encapsulamiento: Nos permite agrupar en una sola abstraccin variables y mtodos, es decir, atributos y computacin. Esa nueva abstraccin es lo que se ha denominado clase. Adems nos permite ocultar informacin de cara al exterior, ya que nos permite definir informacin interna a la clase. Excepciones : nuevos mecanismos de tratamientos de errores y condiciones excepcionales de ejecucin. Esto favorece la reutilizacin del cdigo. Asertos : Nos permite definir ciertas condiciones indispensables para continuar con la ejecucin. Sobrecarga: Nos permite dar distintas implementaciones a una funcin, o mtodo, con el mismo identificador pero dis tintos atributos (sobrecarga de funciones o mtodos). Tambin nos permite cambiar el significado de los operadores normales, de manera que al hacer a + b podemos estar concatenando, o incluso restando. (sobrecarga de operadores). Herencia: Es una relacin de generalidad, donde una clase hereda los atributos y los mtodos de otra. Aqu, la clase de la que se hereda es ms general. Polimorfismo: Permite ejecutar varios mtodos con el mismo mensaje. Genericidad: Nos permite hacer abstracciones genricas respecto al tipo, es decir, podramos hacer una lista de cualquier cosa, sin importar el tipo. Se implementa mediante templates o plantillas. PRIMER PROGRAMA EN C++ El programa tendr el siguiente aspecto: #include <iostream> #include <string> using namespace std; int main(){ cout<<Introduzca el nombre string nombre; cin>>nombre; cout<<Nombre <<nombre<<.<<endl; return 0; } INCLUDE Podemos empezar hablando de los include. En C++, al incluir algo que pertenece a la librera estndar no har falta que pongamos el .h para indicar el archivo de cabecera, pero cuando incluyamos algo no estndar no se nos debe olvidar. Por otra parte, el #include <string> no hace referencia al archivo string.h que se usaba en C, sino a la clase string que nos ofrecer muchas ms funcionalidades. El iostream es la clase que se ocupa de manejar los flujos de E/S.

ENTRADA Y SALIDA Si seguimos nos topamos con Cin y Cout, que bsicamente son los flujos estndar de entrada y salida respectivamente. En realidad son objetos, de la clase stream, que estn implementados en iostream y que est definido en el espacio de nombres (namespace) de la std. Tras ellos nos encontramos con 2 nuevos operadores: <<, operador de salida, y >>, operador de entrada. Quizs suenen a los operadores de C, pero aquellos se dedicaban a hacer movimientos a nivel de bits. En el iostream estn sobrecargados (aprovechando esa caracterstica del lenguaje C++). As, el Cout escribir cualquier tipo simple y objetos de la clase String, siempre en modo texto y nunca en modo binario. El Cin lee los mismos datos, sin necesidad de reservar memoria antes. Al hacer Cout<<nombre se escribir el nombre y adems se devuelve, de nuevo, Cout . Esto permite concatenar salidas, ya que tras hacer la primera salida, Cout<<Nombre:<<nombre se convierte en Cout<<nombre y por la pantalla ya se escribi Nombre: Esto mismo pasa con Cin . El ende final es sencillamente un manipulador. Lo que haces es marcar el final de la lnea y un salto a la siguiente. Hay muchos manipuladores, pero los principales son: endl (establece un final de lnea y vaca el buffer de salida), flush (vaca el buffer de salida), ende(establece el final del string). El vaciado del buffer es importante, ya que antes de escribir en la pantalla todo se almacena en un buffer, para escribirlo ms tarde todo junto. Mediante el flush o el endl , nos aseguramos que el buffer se vace y se muestre por pantalla lo que queremos. No obstante, al dejar de utilizar el Cout e invocar su destructor se hace un flush. CREACIN DE OBJETOS Por otra parte, vemos que se crea un string, llamado nombre, sin necesidad de hacer un new, ni nada parecido. En esto, C++ se diferencia de Java. RETURN Es igual que en C, pero si vamos a devolver 0 se puede obviar. CLCULO DEL MXIMO DE UN VECTOR #include <iostream> #include <cstdlib> using namespace std; #define ELEMENTOS 10; #define INTERVALO 100; #define MAXIMO(a,b) ((a>b)?(a):(b)) int main(){ int vector[ELEMENTOS]; for (int i=0; i<ELEMENTOS; i++){ vector[i] = (int) (((float) rand()/RAND_MAX)*INTERVALO) (INTERVALO / 2)); } int maximo = vector[0]; for (int i=0; i<ELEMENTOS; i++){ maximo = MAXIMO(maximo, vector[i]); } Cout<<maximo;}

INCLUDE Todo lo que funcionaba en C sigue funcionando en C++, pero para hacerlo compatible con el estndar ANSI/ISO se incluye quitando el .h y anteponindole una C. As, cstdlib equivale al stdlib.h . MACROS Se usan de la misma manera que en C, pero siguen siendo un foco de errores, sobre todo porque no se pueden depurar. Por ello, se pueden declarar constantes de otra manera, con la palabra reservada const, muy usada en C++. As, const unsigned elementos = 100; crea una variable constante, que no permitir ninguna variacin, pero que s que permite hacer trazas con ella. A este tipo de variables hay que darles un valor en su declaracin. Para sustituir a las macros con parmetros usaremos simples funciones. Pero eso es ms lento que una macro, as que tendremos que aadir la palabra reservada inline , quedando: inline int maximo(int a, int b){return (a>b)?a:b;} Con esto, se cambia la llamada a la funcin por el cuerpo de la misma y al compilador le llega el cuerpo. Obtenemos as una rapidez igual a la de las macros, pero con la capacidad de depurar. Podemos usarlo con cualquier funcin, pero se suele usar con funciones pequeas, tan solo, porque sino el ejecutable puede ocupar demasiado, y en lugares donde se retrase demasiado la ejecucin (como un bucle). El inline se puede hacer recursivo, pero es muy difcil. GENERICIDAD Para hacer que esa funcin no solo calcule el mximo entre dos enteros, sino entre dos elementos de cualquier tipo, usamos la genericidad. Tenemos entonces: template <Typename T> T maximo(T a, T b){return (a>b)?a:b;} Esta funcin aceptar argumentos de cualquier tipo, pero los dos deben ser del mismo tipo. Pero cmo podemos indicar el tipo de manera explcita? Es decir, en: T *nuevo(){ return (T*) malloc(sizeof(T));} Aqu necesitamos indicar sobre qu tipo vamos a almacenar la memoria. Para ello, en la llamada: int *p; *p = nuevo <int> (); PRIMERA CLASE: CLASE CRONMETRO Cronmetro Parado: int Comienzo : clock_t Final: clock_t + iniciar() + detener() + getmilisegundos() : long

+ estaparado() : int BOOLEANOS Si bien en C++ existe el tipo bool , normalmente usaremos los enteros, al igual que se haca en C. --------------------------------------------------------------------------------------------------------------------CRONOMETRO.H #include <ctime> class Cronometro{ private: int parado; clock_t comienzo; clock_t final; public: void iniciar(); void detener(); long get milisegundos(); int estaparado(); }; --------------------------------------------------------------------------------------------------------------------OCULTACION Hay 3 tipos de ocultacin: private: Solo accesible desde la propia clase. public: accesible desde cualquier punto. protected: accesible desde la clase y sus derivadas. Si no se especifica ninguna de las otras dos, siempre se supondr una ocultacin private. Por eso, se puede omitir a la hora de la implementacin, no as el public o el protected. EL ; Es obligatorio! Sino se pone, el error no nos dar en el .h sino en el .cpp que lo incluya, siendo un foco de dolores de cabeza para el programador. --------------------------------------------------------------------------------------------------------------------CRONOMETRO.CPP #include cronometro.h //Sino no reconocer la clase cronmetro #include <ctime> int Cronometro::estaparado(){ //Con cronometro:: indicamos que no es una funcin normal, sino un mtodo de la clase Cronometro return parado; } void Cronometro::iniciar(){

inicio = clock(); parado = 0; } void Cronometro::detener(){ if (estaParado()){//no accedemos directamente al atributo por encapsulamiento. As, si cambiamos las condiciones de parada no tendremos que cambiar nada aqu throw No puede detenerse; //Es una excepcin, sale del mtodo con ese mensaje } final = clock(); parado = 1; } long Cronometro::getmilisegundos( ){ if (estaParado()){ return ((final inicio)*1000.0/CLOCKS_PER_SEC); //son los ciclos por segundo del PC, ya que clock devuelve el numero de ciclos return ((clock() inicio)*1000.0/CLOCKS_PER_SEC); } ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------PRUEBA.CPP #include cronometro.h #include <iostream> using namespace std; int main(){ Cronometro c; c.iniciar(); unsigned edad; cin>>edad; c.detener(); cout<<c.getmilisegundos()<<ende; c.detener(); } --------------------------------------------------------------------------------------------------------------------CONSTRUCTORES Y DESTRUCTORES Al crear el cronometro lo creamos con valores aleatorios, por lo que al acceder a ellos (bien con getmilisegundos o con otro mtodo) nos devolvern basura. Para evitar esto, normalmente se implementan constructores, mtodos que se llaman al crear el objeto y se usan para inicializar ciertos valores. De hecho, aunque no implementes ningn constructor, se crea el constructor por defecto, que no hace nada. As mismo, al finalizar el programa el cronometro se destruye, pero nace nada especial (a veces nos interesa hacer operaciones cuando destruimos un objeto). Para evitarlo se crean unos mtodos llamados destructores.

Las caractersticas de esos mtodos son: Constructor : o Su nombre es el de la clase: Cronometro. o No devuelve nada. Ni tan siquiera se puede poner void. o Puede recibir cualquier tipo y nmero de argumentos. o Siempre existe uno, por omisin, que no hace nada. Destructor : o Su nombre es el de la clase, pero se le antepone un ~: ~Cronometro o Tampoco devuelve nada, ni se le puede poner void . o No puede tener parmetros. Por tanto, no se puede sobrecargar. o Existe uno por omisin, que tan solo libera el estado del objeto.

Cronometro necesita un constructor, as que se implementa. Pero una vez que lo implementamos, deberemos modificar Prueba.cpp, ya que el constructor por defecto deja de estar disponible en cuanto nuestra clase tiene constructor propio. --------------------------------------------------------------------------------------------------------------------CRONOMETRO.H Cronometro(int); //No se indica el nombre de la variable ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------CRONOMETRO.CPP Cronometro::Cronometro(int iniciado) { If (iniciado){ iniciar(); } else{ parado = 1; inicio =0; final = 0; } } ---------------------------------------------------------------------------------------------------------------------

EXCEPCIONES En el programa se lanza una excepcin, que no controlamos. Cuando esto ocurre se llama a la funcin terminate, la cual a su vez invoca a una funcin llamada abort incluida en el stdlib. As se aborta la ejecucin. --------------------------------------------------------------------------------------------------------------------PRUEBA2.CPP #include cronometro.h #include benchmark.h //Este es un archivo que incluye tambin al cronometro.h #include <iostream> using namespace std;

int main(){ Cronometro c; c.iniciar(); unsigned edad; cin>>edad; c.detener(); cout<<c.getmilisegundos()<<endl; c.detener(); } --------------------------------------------------------------------------------------------------------------------DECLARACION REPETIDA Este programa puede parecer igual que el anterior, pero el compilador recibir: Class Cronometro:{ . }; Class Cronometro:{ . }; Benchmark.h . .. .. . Y nos dar un error Clase Cronometro ya declarada. Para eso usaremos las sentencias del preprocesador: ifndef, define y endif. --------------------------------------------------------------------------------------------------------------------CRONOMETRO.H #ifndef CRONOMETRO_H #define CRONOMETRO_H //Codigo de Cronometro.h #endif --------------------------------------------------------------------------------------------------------------------Cronometro.h

MTODOS INLINE Si queremos mejorar la rapidez de ciertos mtodos (por ejemplo estaparado o getmilisegundos, los declaramos inline, de igual manera que las funciones. En el caso de los mtodos hay dos maneras de hacerlo. Una es la tradicional, poner inline en el .h. La otra manera es codificar el cuerpo de la funcin dentro del propio .h. Si usamos la primera manera, en el cpp no har falta indicar de nuevo que es inline. ---------------------------------------------------------------------------------------------------------------------

CRONOMETRO.H inline long get milisegundos(); //Primera manera int estaparado(){ //Segunda manera return parado;} --------------------------------------------------------------------------------------------------------------------CLASE VECTOR

Vector Elementos: unsigned Memoria: int* + numeroElementos():unsigned + getElemento(unsigned): int + setElemento(unsigned, int) + Vector(unsigned) + ~Vector() --------------------------------------------------------------------------------------------------------------------VECTOR.H #ifndef class Vector{ unsigned elementos; int *memoria; public: Vector(unsigned tam){ elementos = tam; memoria = new int[tam]; } ~Vector(){ delete [] memoria; } Unsigned numeroElementos(){ return elementos; } int getElemento(unsigned i); void setElemento (undigned i, int j); }; #endif ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------VECTOR.CPP int vector::getElemento(unsigned i){ if (i > (elementos -1))

throw Fuera de rango return memoria[i]; int vector::getElemento(unsigned i, int j){ if (i > (elementos -1)) throw Fuera de rango memoria[i] = j; ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------TABLAMULTP.CPP //Crear una tabla de multiplicar #include vector.h #include <iostream> using namespace std; int main(){ int n; cin >> n; vector Tabla(10); for (int i = 0; i< tabla.numeroElementos(); i++){ tabla.setelemento(i, (i+1)*n); } for (int i = 0; i< tabla.numeroElementos(); i++){ cout<<n<<x<<(i+1)<<=<<tabla.getelemento(i)<<endl; } } --------------------------------------------------------------------------------------------------------------------RESERVA DE MEMORIA Hasta ahora reservbamos memoria con malloc. Ahora podemos usar tambin el operador new (antecedente del new de java). Para ello, simplemente tenemos que indicarle al new de que tipo queremos reservar la memoria, y nos devolver un puntero a la zona de memoria, creada en la Heap (memoria dinmica). As, para crear un entero: int *p = new int; La diferencia con malloc es que con new podemos pasarle parmetros al constructor de la clase: Cronometro *c = new Cronometro(0). Aunque, si no queremos pasarle parmetros, podemos obviar los parntesis. Para liberar esa memoria ya no usamos free. Usamos delete. La ventaja de delete es que, si se trata de un objeto, llama al destructor del mismo. Con new tambin podemos crear arrays, pero entonces, en delete, deberemos indicarlo con unos corchetes: delete [] cadena. Podemos no ponerlos, o ponerlos cuando no debemos, pero entonces habr errores. Adems, si creamos un array de objetos, no podremos pasar ningn parmetro al constructor, por lo que har falta uno por omisin. Normalmente lo que se hace es crear un array de punteros a los objetos.

Tanto delete, como new, son operadores, as que se podrn sobrecargar. Adems, en caso de que no haya memoria, se lanza una excepcin bad_alloc, que se encuentra definida en el archivo <new>, por lo que habr que incluirlo si pretendemos capturarla. Por ltimo, podemos no asignarlo a nada: cout>>((new Cronometro(1))->getmilisegundos()). Pero entonces no llamaremos a su destructor ni podremos liberarlo. As, si queremos crear un array de vectores: --------------------------------------------------------------------------------------------------------------------TABLAMULTP2.CPP //Crear las tablas de multiplicar del 1 al 10 #include vector.h #include <iostream> using namespace std; int main(){ int n; cin >> n; vector *Tabla[10]; for (int i = 0; i< 9; i++){ vector[i] = new Vector(10); } // Tratamiento con las tablas for (int i = 0; i< 9; i++){ delete(vector[i]); } } --------------------------------------------------------------------------------------------------------------------De manera similar haramos para crear talas de vectores, en lugar de arrays. CLASES AMIGAS Dos clases se pueden declarar como amigas, de manera que los mie mbros de una clase, A, declarada amiga de otra clase, B, pueden acceder a los miembros de todas las instancias de B. Normalmente esto sirve para inutilizar el encapsulamiento y la proteccin de informacin. En ciertas ocasiones puede haber casos de referencia circular, es decir, una clase hace referencia a otra que, a su vez hace referencia a la primera. Por ejemplo, una clase lista hace referencia a una clase nodo, declarada como amiga de la clase lista. Para evitarlo se nombra a la clase lista antes de la clase nodo, de una manera sencilla: class lista; PARMETROS POR OMISIN Como su nombre indica, se trata de dar valores por omisin a los parmetros, de manera que si en la llamada a la funcin no se incluyen , se seguirn teniendo valores. Para ello se pondr una asignacin en la definicin de la funcin, o bien en el momento de la llamada. As, en: void f(int a, float b= 0.0, char c= , chas *s= )

Los parmetros b, c y s siempre tendrn un valor y se pueden omitir en la llamada. Al definir los parmetros por omisin, siempre se deben poner a la derecha y por estricto orden que no podremos saltarnos en la llamada. Si queremos darle un valor a c, habr que drselo a b. Se puede usar tambin en mtodos y es til para los constructores. As, en nuestra clase Vector, podemos hacer el constructor con parmetro por omisin: Vector (unsigned tam = 10) , de manera que ya podremos crear arrays de vectores de manera normal y todos se inicializan con ese constructor, aunque no le pasemos ningn parmetro, ya que tiene uno por omisin. OPERADOR DE MBITO Imaginemos que nuestro constructor de vector fuera: unsigned elementos; .. Vector(unsigned elementos){ elementos = elementos; Esto nos dara un error. Hay que indicarle cual de los dos elementos es el atributo de la clase. Para ello se usa el operador de mbito ::. Lo que tendramos que hacer es: Vector::elementos = elementos; NAMESPACE, ESPACIO DE NOMBRES Es una agrupacin de abstracciones (macros, clases, objetos) que sirve para evitar la colisin de nombres y para organizarse mejor, ya que permite la agrupacin de abstracciones relacionadas. Adems, es otro paso ms en la tcnica Divide y vencers, tan comn en programacin. Por otra parte, nos evita tener que usar los operadores de mbito de manera que hagan ilegible el cdigo. De hecho, en todos los programas hemos usado un namespace, el std, que contiene referencias a elementos como los operadores --------------------------------------------------------------------------------------------------------------------COLECCIONES.H namespace Colecciones{ class Vector{ . }; class Lista{ . }; class Cola{ . }; } ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------PRUEBA.CPP #include colecciones.h Int main(){

Colecciones::Vector(10); using Colecciones::lista; Lista list; using namespace Colecciones; Vector V (10); Cola C; .. } --------------------------------------------------------------------------------------------------------------------PROGRAMACIN POR CONTRATO PRECONDICIONES Y POSTCONDICIONES Las precondiciones son las condiciones que deben cumplir todos los datos antes de la ejecucin de un mtodo, o funcin. Estas precondiciones suelen ser excepciones, que obligan al cliente (usuario del mtodo o funcin) a meter de manera correcta los datos. Se usan sobre todo en la programacin por contrato, ya que el cliente no sabe nada del cdigo interno de las funciones y no las puede modificar. Las poscondiciones son condiciones que se deben cumplir despus de la ejecucin de la funcin o mtodo. Se suelen usar para comprobar resultados de frmulas, o relaciones entre los atributos de una clase Las poscondiciones suelen consistir en asertos, sentencias que hace que el programa casque, en tiempo de depuracin, en caso de que alguna relacin no se cumpla. Por lo tanto obligan al desarrollador a crear sus programas de una manera correcta. Hay una tercera opcin, las invariantes de clase, que son condiciones que deben cumplirse, dentro de la clase, para que la instancia de la clase sea correcta. Las invariantes tambin obligan al desarrollador a realizar sus clases y programas de manera ordenada y elimina posibles errores. Estos conceptos se usan, sobre todo, en la llamada programacin por contrato . En el caso de nuestra clase cronometro, una invariante podra ser Si el cronometro est parado, final >= inicio. Pero hay que traducirlo a un lenguaje de y y o, por lo que quedar No est parado, o final es mayor o igual que inicio . ASSERTS El fichero a incluir para poder manejar asertos es cassert. En ese fichero est la funcin void assert(int), que es la que se usa para manejar los asertos. Lo ms normal es llamar a la funcin pasndole alguna sentencia lgica que pueda evaluar. Si la sentencia devuelve 0, saltar el assert. Cuando el assert salta el programa casca directamente, as que no se puede manejar, a diferencia de una excepcin. La otra diferencia es que solo saltan si estamos en modo de depuracin. Cuando la ejecucin se compila definitivamente, se salta esas llamadas. El modo en que estamos se suele indicar con la macro NDEBUG (Not Debug) no est definida. REFERENCIAS O ALIAS Es un identificador cuya zona de memoria es la de una variable a la que hace referencia.

int a = 1; int &r = a;//As,r se queda apuntando a la direccin de a Adems, como no tienen zona de memoria propia, siempre deben tener asignada una. Por ello es obligatorio definirlas en su declaracin, y solo ah, ya que no se les puede cambiar el valor. Por ello, no tiene nada que ver con los punteros (que son dinmicos, ya que su valor cambia), como se ve aqu. int a = 1; int &r = a;//referencia int *p=&a;//puntero r = 2;//Esto cambia el valor de r y a *p = 3;//Esto cambia el valor de r y a, pero no el de p que apunta al mismo sitio RESTRICCIONES Una referencia de un tipo T ha de inicializarse con un LValue (LValue = RValue) de tipo T. As: int &r = 3;//NO. 3 es un RValue. No se puede poner 3 = a const int c=0; int &r = c; // NO. c es un RValue. No se puede poner c = a char ch = a; int &r = ch;// NO .ch es de otro tipo Si, en cambio, una referencia es de tipo const T, lo puedo inicializar con cualquier T o promocionable a T (el char promociona a int , el float a double ). As: Const double &rd = 3;//Es valido, 3 promociona a double CONST Como vimos, crea constantes de los tipos bsicos, pero tambin puede crear objetos constantes. Por tanto, en esos objetos, no se podr modificar el estado de ninguno de sus atributos, ni desde fuera ni desde sus mtodos. Ahora bien, el C++ no distingue entre aquellos mtodos que modifican el estado del objeto (modificadores) y aquellos que no lo hacen (selectores). Tendremos que ser nosotros quienes distingamos entre ambos. La palabra const nos permitir hacer esa distincin. As, tras establecer la lista de parmetros, tendremos que poner la palabra const para indicar que el mtodo es selector, tanto en la definicin como en la declaracin. CRONOMETRO.H int estaparado() const; CRONOMETRO.CPP int Cronometro::estaparado() const{ }

Si tratamos de modificar un atributo en un mtodo declarado como selector, nos dar error de compilacin. PASO DE PARMETROS Los parmetros se pueden pasar de 3 maneras distintas: PASO POR VALOR El parmetro formal es una copia del parmetro real. As, en: Void Hola (int n){cdigo de la funcin} Hola(3); El parmetro formal, n, es la copia del parmetro real, 3 . PASO POR DIRECCIN El parmetro formal es un puntero que apunta al parmetro real. Lo que se hace en realidad es pasar un puntero por valor, ya que el parmetro formal es una copia del parmetro real, que no es ms que un puntero. PASO POR REFERENCIA El parmetro formal es un alias construido sobre el parmetro real, de manera que se trata como el propio parmetro. En el caso del return , al devolver una referencia podramos usarlo como un LValue. As, por ejemplo: VECTOR .H Class Vector{ int &elemento(unsigned i){ return memoria[i];} } Podramos hacer: v.elemento(1) = valor; valor = v.elemento(1); EJEMPLO #include entero.h #include <iostream> Entero Global(1); Entero valor(Entero e){e.setEntero(-1); return global;} Entero *dir(Entero *e){e->setEntero(-1);return &global;} Entero &ref(Entero &e){e.setEntero(-1); return global;}

int main(){ Entero local(2); valor(local).setEntero(-2); dir(&local)->setEntero(-2); ref(local).setEntero(-2); } En este ejemplo tenemos los 3 diferentes pasos ejemplificados, donde el estado de la pila ser: Valor global 1 local 2 Copia de local: 2 -1 Copia de global: 1 -2 Direccin global 1 -2 local 2 -1 Puntero a local Puntero a global Referencia global 1 -2 local 2 -1 Alias a local Alias a global

COPIA DE OBJETOS CONSTRUCTOR DE COPIA El paso por valor puede ser problemtico porque implica una copia del objeto. En este caso, el objeto local de la clase Entero se copia para darle un valor al parmetro real. Cuando se acaba la funcin, dicho parmetro real se destruye. Algo parecido pasa con la copia de global que se devuelve. La clase Entero es una clase simple. No posee punteros a otras estructuras ni atributos que sean otras clases. Por ello, la copia consiste en una mera copia de sus atributos que, al ser destruidos, no dan problemas. Imaginemos ahora que pretendemos hacer lo mismo con la clase Vector. La clase Vector posee un puntero a la zona de memoria donde se almacenan los datos del vector. Al hacer una copia el nuevo vector no posee una copia de los datos de la memoria, sino una copia de un puntero que apunta a ellos. Por ello, si hicisemos un paso por valor, como en el ejemplo escrito, al destruirse el parmetro formal, la zona de memoria a la que apunta se convierte en basura. Quedando tambin inservible para el vector del que hemos hecho la copia, el parmetro real. Por ello, tendremos que controlar la duplicacin de los objetos, mediante un mecanismo proporcionado por C++, el constructor de copia. Su sintaxis sera: Vector(Vector &vector); Vector(const Vector &vector); Segn queramos modificar o no el Vector del que hacemos la copia. Lo aadimos entonces a la clase vector. VECTOR.H Class Vector{ Vector(const Vector &vector); };

VECTOR.CPP Vector(const Vector &vector){ memoria = new int[elementos = vector.elementos]; for (int i=0; i< elementos; i++){ memoria[i] = vector.memoria[i]; } } C++ llamar a este constructor de copia cada vez que se haga una copia. Pero, al igual que ocurre con los constructores y destructores normales, tambin existe un constructor por defecto, que simplemente copia el estado binario del objeto. Tambin puede suceder que queramos impedir las copias de objetos. Para ello simplemente tendremos que declarar el constructor de copia en la zona privada de la clase. De esta manera deshabilitamos el constructor de copia por defecto, pero nos aseguramos que ninguna otra clase pueda acceder a l. REGLAS DEL PASO DE PARMETROS Hay varios casos: - Modificamos el parmetro real: referencia o direccin. - No Modificamos el parmetro real: valor. - No se DEBE modificar el parmetro real: referencia constante. Normalmente, si el dato a pasar es muy grande, se evita la copia, para no saturar la memoria. SOBRECARGA DE MTODOS Y FUNCIONES Para sobrecargar mtodos o funciones habr que variar el nmero y tipo de parmetros. De esta manera: int menos (int a){return a;} double menos (double a){return a;} int menos (int a, int b){return a-b;} double menos (double a, double b){return a-b;} Son validos, pero: long int menos (int a, int b){return a-b;} No es vlido ya que solo cambia el tipo devuelto. SOBRECARGA DE OPERADORES Esta sobrecarga no tiene que ver con la anterior, ya que esta sobrecarga nos da una libertad mucho mayor. La sobrecarga se permite sobre operadores unarios (+, -, []) o binarios, no as sobre el terciario (_?_:_) y tanto en forma de miembro de clase como de funcin. Adems podemos sobrecargar el operador bien como mtodo modificador o como selector, o de las dos maneras. SOBRECARGA DEL OPERADOR BINARIO + Podemos escoger la manera de escribirlo (como mtodo o como funcin), teniendo en cuenta que se lo aplicaremos a nuestra clase Vector.

Nos queda entonces: Vector::operator+(Vector a) // como miembro operator+(Vector a, Vector b)// como funcin Vector v1(0), v2(3); v1 + v2;//equivale a v1::operator+(v2) u operator(v1, v2) SOBRECARGA DEL OPERADOR UNARIO Podemos expresarlo de las dos maneras: Vector::operator-() // como miembro operator-(Vector a)// como funcin SOBRECARGA DEL OPERADOR = Al igual que ocurra con el constructor de copia hay un operador de asignacin por defecto. Y al igual que ocurra con el constructor de copia, la finalidad del operador por defecto es copiar el estado binario de los objetos. Esto provoca los mismos problemas que el constructor de copia por defecto, por lo que, en objetos no simples, deberemos especificar el operador de asignacin. Adems podemos prohibir la asignacin poniendo el operador = en la parte privada. Este operador es el nico que no se hereda. RESTRICCIONES DE LA SOBRECARGA DE OPERADORES Un operador solo se puede sobrecargar de una de las dos maneras, nunca de las dos simultneamente. Los parmetros y valores devueltos no se pasan por direccin sino por referencia o valor. Se mantiene la precedencia de operadores. No podemos inventarnos operadores. No podemos usar parmetros por omisin. CONSTRUCTORES Y SOBRECARGA EN CLASE VECTOR class Vector{ unsigned elementos; int* memoria; public: explicit Vector(unsigned n){ memoria = new int[elementos = n];} ~Vector(){ delete []memoria;} Vector(cost Vector& v); Vector (int[] v, unsigned n); Vector( const string& s); Unsigned numeroElemento() const {return elementos;} Vector operator+(const Vector& v) const;

int operator==(const Vector& v) const; Vector &operator+=(const Vector& v); //devolveremos el operador implcito por referencia int operator[](unsigned n) const; //no queremos que se modifique, por lo que devolveremos una copia. Corresponde a valor = v[3]; int &operator[] (unsigned n); //por referencia porque se modificar, corresponde a v[3] = valor //si el objeto no es constante llamar a este operator, de lo contrario llamar al otro, por lo que no deberan faltar ninguno }; ostream &operator<<(ostream &o, const Vector &v) //En el operator << deberamos modificar la clase ostream, que es quien lo llama en realidad. Como no tenemos tanto acceso tendremos que hacerlo en forma de funcin

CONSTRUCCIN ANNIMA Es la construccin de un objeto de manera que no le asignamos identificador, como en la instruccin: cout<<Vector(string(Hola))<<endl; Aqu realizamos dos construcciones annimas, el Vector y el string. Se construye en la pila un objeto annimo cuyo mbito es la propia instruccin en la que se declara. Al terminar dicha instruccin se invoca su destructor y no se puede recuperar. Esto equivaldra a hacer un new en Java, pero en Java la gestin de memoria es automtica. En C++ deberamos hace un delete para liberar la memoria, y al perderse toda referencia al objeto annimo, no podramos. PROMOCIN Y CONVERSIN DE TIPOS Un constructor con un solo parmetro implica una conversin del tipo del parmetro al tipo de la clase. As, en la clase Entero haba un constructor que solo reciba un int. Por esto, toda funcin que requiera recibir un objeto Entero aceptar un int. Eso ocurre tanto en llamadas a funciones como en asignaciones. As: void f(const Entero &e){cout<<e.getEntero();} f(3); Entero a; a = 4; Hemos de ser cuidadosos. Si tuviera otro constructor para un unsigned debera indicar que deseo usar el unsigned en lugar del entero, mediante una conversin. a = (unsigned) 4; Pero puede ser que no queramos usar esta promocin de tipos. Por ejemplo, en la clase Vector no nos interesa que un unsigned pueda promocionar a Vector. Para evitar esta promocin no deseada debemos usar la palabra reservada explicit en la declaracin del constructor.

explicit Vector(unsigned n){ memoria = new int[elementos = n];} Esta es una herramienta muy poderosa, sobre todo si la combinamos con la sobrecarga de operadores. As, podramos hacer: cout<< v + string(Hola)<<ende; No hace falta sobrecargar el operador suma con un string, ya que el string promociona a Vector y ya tenemos el operador suma entre dos vectores. THIS Todo mtodo (hablamos por tanto de una clase) no esttico permite el uso de la palabra reservada this, que no es ms que una referencia al objeto que realiza la llamada. Esto es til para posibles casos de ambigedad: Class Entero{ int n; public: Entero(int n){ This->n = n;//n = n es pura ambigedad } }; Tambin es til para enlazar unos objetos con otros. MIEMBROS ESTTICOS Las variables de clase son aquellas que se usan porque afectan a toda la clase y no a un solo objeto. Podramos tener una clase coche:

Coche Color: String id: unsigned NumeroCoches: unsigned NumeroRuedas: unsigned + getColor(): string + getNumeroCoches(): unsigned + getNumeroRuedas(): unsigned En este diseo, lo referente al nmero de coches debera ser ajeno a los objetos, de manera que se pueda consultar aunque no haya objetos construidos. Adems, para asignar una id a cada objeto hace falta saber el nmero de coches que ya hay construidos. Por otra parte, el nmero de ruedas ser constante para todos los coches, por lo que podemos establecerlo como una variable de clase.

COCHE.H

#ifndef COCHE_H class coche{ string color; unsigned id; static unsigned numeroCoches, numeroRuedas; public: coche(const string &c){ color = c; id = ++numeroCoches; } const string &getColor() const{return color;} static unsigned getNumeroCoches(){return numeroCoches;} static unsigned getNumeroRuedas(){return numeroRuedas;} }; A los mtodos estticos no se les puede poner el const, aunque sean selectores y no modificadores. La razn es sencilla. Si no necesitan de un objeto construido, no tiene sentido pedir que el objeto permanezca constante. Adems, el static solo se debe indicar en su declaracin. A la hora de implementarlos en el cpp no debemos repetirlo. Por otra parte, los atributos de clase han quedado sin definir, ya que no se pueden definir en el .h. El valor inicial se le debe dar en el .cpp. COCHE.CPP #include coche.h unsigned coche::numeroCoches = 0; unsigned coche::numeroRuedas = 4; A la hora de llamar a los miembros estticos podemos usar tanto la clase como el objeto, es decir, la llamada la podremos realizar coche::getNumeroCoches() o ibiza.getNumeroCoches() . Si bien la ltima puede parecer ms engaosa porque se puede interpretar que es un mtodo del objeto y no de la clase. Lo nico en que debemos fijarnos en el operador de mbito para cada caso. RESTRICCIONES EN MIEMBROS ESTTICOS En un mtodo de clase no e puede usar la referencia this. Esto es lgico, ya que no se requiere de un objeto construido. Desde un mtodo de clase no se puede acceder a un atributo de instancia, ya que si no hay instancia construida, cmo vamos a usar un atributo? Por la misma razn, desde un mtodo de clase no se puede acceder a un mtodo de instancia. Un miembro esttico no puede ser virtual, ya que el polimorfismo no es entre clase, sino entre objetos. Un mtodo de clase no puede ser selector, ya que esto implicara la existencia de un objeto que no se modificar.

CLASES DE UTILIDAD En muchos lenguajes de POO los tipos simples no tienen clases asociadas (no como en Java). Sin embargo, la tendencia a alejarnos de las funciones y a agruparlo todo en clases nos lleva a la creacin de clases de utilidad o utility, que se caracterizan por tener todos sus miembros estticos. Un ejemplo de este concepto es la clase Matematicas, correspondiente a la Math de Java. <<utility>> Matematicas PI: Double E: Double - matematicas() + Seno(double): double Uno de los rasgos de las clases de utilidad es que el constructor est en la parte privada, de manera que no se pueden construir objetos de esa clase. Otra solucin es usar un namespace en lugar de una clase de utilidad. HERENCIA CONCEPTO BSICO Es una relacin de generalizacin, de lo general a lo especfico, entre dos clases. GENERAL

ESPECFICO La clase ms general se llama clase base (superclase en Java) y la especfica se denomina clase derivada (subclase en Java). La clase derivada adoptar el comportamiento de la clase base, lo que quiere decir que a sus propios mtodos se aadirn aquellos que haya heredado. Adems, la herencia es algo transitivo. Una clase derivada puede ser clase base para otra, por lo que esta ltima heredar los mtodos y atributos de las otras dos clases que estn por encima. USOS La herencia tiene dos grandes usos: La ampliacin (de ah proviene el extends de Java), consistente en aadir mtodos y atributos a una clase, lo que se conoce como herencia de implementacin. Sin embargo, la mejor manera de hacer esto es por composicin. La especializacin , consiste en reimplementar mtodos de la clase base para especializar un comportamiento LA HERENCIA COMO AMPLIACIN La ampliacin es una operacin que se lleva a cabo cuando queremos aadir una funcionalidad pero no podemos acceder a la clase que queremos ampliar, como sucede en la clase vector de C++.

Como ya hemos dicho, para una ampliacin se suele realizar una composicin, consistente en usar la clase base como atributo de la derivada, pero hay casos en los que se hace mediante la herencia. Tenemos as: Consulta Palabra: string evaluar(const string &): int getpalabra(string) setpalabra (string &s)

ConsultaAparicion apariciones(const string&): unsigned

CONSULTA.H #include <string> using namespace std::string; class Consulta{ string palabra; public: consulta(cont string& p){palabra = p;}( const string &getpalabra() const{return palabra;} void setpalabra(cont string& p){palabra = p;} int evaluar (const string &t) const; }; Class consultaApariciones. public consulta{ //la herencia se hace con public, no con private, porque luego los atributos y mtodos de la clase base seran privados y traera problemas public: unsigned apariciones (const string& texto) const; }; CONSULTA.CPP #include consulta.h int consulta::evaluar(const string &t) const{ return (t.find(palabra) != string::npos); //find encuentra la cadena palabra dentro de la cadena t. Si no est devuelve una posicin igual a string::npos. No es acumulativo. Siempre encuentra la primera ocurrencia. } unsigned apariciones (const string& texto) const{ int pos = 0; unsigned apar = 0; while((pos = t.find(palabra, pos)) != string::npos){

apar++; } return apar; } CONSTRUCTORES EN LA HERENCIA Los constructores no se heredan. Por eso, a la clase consultaAparicin no es posible crearla pasndole un string, ya que no tiene definido constructor. Un constructor de una clase derivada debe invocar a un constructor de la clase base. Es lo que se conoce como una lista de inicializacin. Se ve en el ejemplo: class A{ int n; public: A(int nn){ n= nn;} }; class B: public A{ string s; public: B(int a, string b): A(a){ s = b; } Como vemos, hemos de invocar al constructor que deseemos y hemos de procurarnos los datos necesarios para proporcionrselos. Por omisin, la Lista de Inicializacin llama a los constructores base por defecto (sin parmetros) As, si en el ejemplo hubiramos hecho: B(int a, string b) { s = b; } Se producir un error de compilacin, ya que la clase A ha perdido su constructor por defecto. La clase consultaAparicin no tiene constructor, por lo que al invocarse el constructor por defecto llamara a los constructores por defecto de sus clases base, que ya no existen. Por eso mismo habr que proporcionarle un constructor. CONSULTA.H ConsultaAparicion(const string& s):consulta(s){} Los constructores se ejecutan por orden, del ms general al ms especfico.

As, se ejecutara antes el constructor de la clase consulta. Los destructores se ejecutan en orden inverso, del ms especfico al ms general. As, se ejecutara antes el destructor de la clase consultaAparicion. LISTA DE INICIALIZACIN AL CONSTRUIR AGREGADOS La lista de inicializacin se puede usar no solo para inicializar la clase base (Nombre de la Clase ()) , sino para inicializar aquellos atributos que pertenezcan a otra clase (Nombre del Atributo ()). Se puede hacer igual con los atributos que sean tipos simples. OCULTACIN DE MTODOS Una clase derivada que implemente un mtodo con el mismo identificador que otro situado en su clase base oculta el mtodo base con igual identificador, aunque tengan distinta signatura. As: class A:{ void m1() const{ cout<<a::m1();} void m2() const{ cout<<a::m2();} }; class B: public A{ void m1() const{ cout<<b::m1();} void m2(int a) const{ cout<<b::m2(int a);} };

Si construimos un objeto de la clase B (B b;): b.m1(); //Escribe b::m1() b.m2();//Error de compilacin. El mtodo de la clase B necesita un parmetro y el mtodo de la clase A est oculto. b.A::m2();//Escribe a::m2() b-A::m2()//Escribe a::m2() HERENCIA COMO ESPECIALIZACIN Este es el uso comn de la herencia. CONSULTA.H class consultaNO: public consulta{ public: consultaNO(const string& s): consulta(s){} int evaluar(const string &text) const{ return !consulta::evalauar(texto);} }; POLIMORFISMO Otra de las ventajas de trabajar con herencia pblica es el polimorfismo.

El polimorfismo nos permite, si trabajamos con referencias o punteros, promocionar una clase derivada a una clase base. De esta manera, si tenemos una funcin: void ref(consulta &c){} void dir(consulta *c){} Esa funcin nos permitir que le pasemos un objeto de la clase consulta o de cualquiera de sus derivados, consultaNO o consultaApriciones. Lo miso nos pasar al otorgarle un valor a consulta *c; consulta &c2; Pero, lgicamente, no podremos acceder a todos los mtodos del objeto sino a los mtodos de la clase base que estemos manejando. En este caso invocar el mtodo apariciones, presente en la clase consultaApariciones, sera un error de compilacin, ya que el compilador no nos puede asegurar que ese objeto sea de esa clase derivada. MTODOS VIRTUALES El problema viene a la hora de usar mtodos ocultos. Si quisiramos invocar al mtodo evaluar, aunque el objeto al que apunte el puntero, en realidad, sea una consultaNO, se ejecutar el mtodo evaluar de la clase consulta. Esto en Java no ocurre gracias al enlace dinmico. Podemos arreglar el entuerto si usamos los mtodos virtuales. Para declarar un mtodo virtual simplemente hemos de anteponer en su declaracin la palabra virtual. CONSULTA.H class consulta{ virtual int evaluar De esta manera, el mtodo declarado en la clase consultaNO tambin ser virtual, ya que posee la misma signatura. Sin embargo, es conveniente que en la declaracin del mtodo de la clase derivada se anteponga tambin la palabra virtual. DESTRUCTORES VIRTUALES Ya hemos visto como hacer que se llame al mtodo de la clase correcta. Ahora imaginemos un escenario parecido en los destructores. Tenemos un puntero a una consulta y le aplicamos un delete. Por defecto se llamar al destructor de la clase consulta. Por eso hay que declarar los destructores como virtuales. ABSTRACCIN Un mtodo abstracto no identifica un mtodo como tal, sino un mensaje. Se usan cuando se quiere identificar un mtodo en una clase base pero no se desea dar su implementacin. En el siguiente diseo de un sistema de figuras, podra tener sentido que los mtodos mostrar y ocultar fueran abstractos.

Figura x,y: int getx () gety () mover(int, int) mostrar() ocultar() Estavisible():int Cuadrado Ancho, alto: int mostrar() ocultar() Circulo Radio: int mostrar() ocultar()

La declaracin de los mtodos mostrar y ocultar, en la clase figura , sera: virtual void mostrar() = 0; virtual void ocultar() = 0; De esta manera, la clase figura sera una clase abstracta, de la que no se podran crear objetos (lo que conlleva que no se pueda pasar en una funcin por copia). La nica utilidad de esta clase sera la posibilidad de crear punteros hacia ella y aprovecharos as de las ventajas del polimorfismo. El hecho de que no se puedan crear instancias de esas clases no implica que no puedan tener constructores, ya que en la Lista de Inicializacin de las clases hijas tendrn sentido. Las clases hijas de una clase abstracta sern, a su vez, abstractas, a no ser que implementen el mtodo abstracto correspondiente. En este caso, crculo y cuadrado debern implementar mostrar y ocultar. PATRN DE DISEO TEMPLATE Este diseo implica que un mtodo de una clase abstracta llame a un mtodo abstracto. De esta manera se deja parte de la implementacin del mtodo a las clases hijas. Un ejemplo podra ser calcular el peso de un s lido. El mtodo getPeso() podra multiplicar un atributo densidad por lo que devuelve un mtodo abstracto, getVolumen() . De esta manera, parte del clculo del peso recae sobre las clases hijas y su implementacin de getVolumen(). HERENCIA MLTIPLE Se produce cuando una clase hereda directamente de ms de una clase. No debera causar problemas esta posibilidad, ya que si una clase A hereda de otras dos, B y C, significa que A es ms especfico que B y que C. Por ejemplo, la clase CuboHelado hereda de la clase Cubo y de la clase Helado, ya que es ms especfica que ellas. La llamada a los constructores se har en el orden en que se indique la herencia, no el constructor usado. As, en class C: public B, public { C(): A(), B(){} }

Se llamara primero al constructor de B y luego al de A. COINCIDENCIA DE NOMBRES Ocurre cuando una clase hereda un mismo miembro de 2 o ms clases distintas. Icono Fichero: string mostrar() IconoReloj IconoReloj hereda dos miembros llamados void mostrar(). Si bien en este caso se trata de dos mtodos, puede darse el caso con dos atributos. Para acceder a esos mtodos no podemos invocar al mtodo simplemente. Iconoreloj ir(reloj.ico); ir.mostrar(); //error de compilacin ir.Icono::mostrar(); ir.Reloj::mostrar(); Normalmente se redefine el mtodo y lo implementamos de nuevo, incorporando los comportamientos heredados. Tenemos as polimorfismo mltiple. HERENCIA REPETIDA Se produce cuando se hereda ms de una vez de la misma clase. No es posible heredar dos veces de la misma clase de manera directa, por lo que debe ocurrir de manera indirecta: A Reloj mostrar()

D Lo que ocurre aqu es una coincidencia de nombres masiva, ya que se duplicarn todos los miembros de la clase base A. Hemos de evitar que esto ocurra indicando que se mire si la clase de la que se va a heredar ya ha sido incluida en la clase actual. Si ocurre as, se debe crear el enlace virtual. En C++ esto se hace usando la herencia virtual: class A { }; class B: virtual public A { }; class C: virtual public A { };

class D: public B, public C { }; Lo que ocurre entonces es que al construirse la clase D primero se construye la clase B, luego la clase A (como clase base de B), luego la clase C y, al ir a construirse la clase A de nuevo, se crea un enlace virtual, de manera que la clase C reconoce a la clase A, ya construida, como su clase base. INTERFACES Una de las posibilidades de la herencia es la creacin de interfaces. Imaginemos que queremos un programa que haga posible que mostremos clases tan dispares como Cronmetros, Vectores y Esferas, cada clase debe mostrar cierta informacin. Podramos crear un mtodo mostrar en cada clase y invocar dicho mtodo al mostrar cada objeto. Pero, para hacerlo de una manera ms flexible, crearemos una clase por encima, con un mtodo virtual puro mostrar(). Haremos que Cronometro, Vector y Esfera hereden de la clase mostrable, de manera que podamos tener un vector de mostrables y no tengamos que saber de que clase es cada uno. Tambin podemos crear clases nuevas, que hereden de mostrable y de cada una de las otras clases, de manera que VectorMostrable herede de Vector y de Mostrable, etc. Esta herencia no es general/especfico, es una herencia de funcionalidad. <<interface>> Mostrable vector<Mostrable *> mostrar() ~mostrable() Cronometro mostrar() Vector mostrar() Esfera mostrar()

Cada vez que creemos un cronmetro, una esfera o un vector, lo aadiremos al Vector esttico de la clase mostrable, si bien no es necesario en una interface. Esto nos permitira tener un mtodo esttico que nos muestre todas las instancias creadas. A este diseo en que se separa la interfaz de la implementacin se le denomina diseo bridge. Hay miles de ejemplos de interfaces, como la posibilidad de crear una interfaz comparable, que nos permita comparar cubos con vectores y ordenarlos mediante diversos algoritmos. DYNAMIC CAST Imaginemos el ejemplo de la interfaz comparable . Incorporara un mtodo, virtual puro, comparar (comparable *), cuyas clases derivadas deberan heredar con la misma signatura. Esto provocar que la clase cubo no pueda definir un mtodo comparar (cubo *) , o de lo contrario no habr polimorfismo. Sin embargo, para comparar un cubo, necesitamos saber si la otra clase tambin es un cubo. Podramos hacer: int comparar (comparable *c){ return (this->getVolumen() c->getVolumen()); }

Sin embargo, no podemos saber si c apunta a un cubo, por lo que no podemos realizar la llamada al mtodo getVolumen, ya que podra tratarse de un vector o cualquier otra clase que derive de comparable. Es necesario, pues, saber la clase concreta de ese puntero. Pasamos de trabajar con las clases bases (Up Cast) para trabajar con las clases derivadas (Down Cast). Se nos puede ocurrir hacer un cast: (cubo *) c Pero esto es muy peligroso, ya que si el objeto no es un cubo ocurrir una excepcin. En Java podemos usar la sentencia instanceof. En C++ recurriremos a un aditamento que se le aplic al lenguaje, el RTTI (Run-Time Type Information). Como es un aditamento no suele estar activado (adems, necesitar la inclusin de cdigo de control), por lo que deberemos indicar su activacin. En Visual Studio .NET se indica en las propiedades del proyecto, en el apartado del lenguaje. La sintaxis del cast dinmico sera: dynamic_cast<T *> puntero_al_objeto. El valor devuelto ser 0, si el objeto no pertenece a la clase T, o el puntero, si es que pertenece. La sintaxis del mtodo ser, finalmente: int comparar (comparable *c){ Cubo *cc = dynamic_cast<Cubo *> (c); If (!cc){throw No se puede comparar.;} return (this->getVolumen() c->getVolumen()); } CONTROL DE EXCEPCIONES El control de errores ideal debe ser extensible (permitir cierto poder de abstraccin), reutilizable (manejar el error de diversas maneras) y robusto (el sistema no puede ignorar el error sino que debe tratarlo). La programacin estructurada no permite, mediante las soluciones que aporta, cumplir estos objetivos. Una primera solucin es retornar valores especiales. Esto se usaba en muchas funciones, incluso del propio lenguaje, pero nada nos obligaba a prestar atencin a dicho valor (lo cual elimina la robustez) y hay casos en los que carece de sentido. Por ejemp lo, si tenemos una funcin que puede devolver cualquier entero, Qu valor especial le otorgamos? Otra solucin es la finalizacin directa del programa. Esta solucin es robusta, no la podemos ignorar, pero no es extensible ni reutilizable, sin contar con que no es deseable perder el trabajo de la aplicacin porque haya una lista vaca, por ejemplo. Otra solucin sera la comprobacin, a priori y a posteriori. A priori compruebas el estado del objeto, a posteriori compruebas si hubo errores. Sin embargo, esto no es robusto, ya que nada me obliga a realizar dichas comprobaciones. Las excepciones nos proporcionan la manera ideal de tratar los errores. Lo que haremos ser separar el punto donde se comete el error del punto donde se trata, de manera que la exc epcin transfiera el flujo de la ejecucin, del punto donde se dio el error al punto donde se tratar, a la vez que proporciona informacin sobre dicho error. Ganamos entonces en robustez, extensibilidad y usabilidad.

EXCEPCIONES EN LA LIBRERA ESTNDAR La Librera Estndar (stdlib) no lanza todas las excepciones que debiera, sino que las combina, por meros motivos de eficiencia, con otras tcticas de tratamiento de errores como las condiciones a priori o posteriori. Hay numerosas excepciones en la stdlib . Algunas de ellas son: Exception : Es la clase base de todas las excepciones de la librera estndar.

Si queremos definir una nueva clase de excepcin podemos derivar de sta. Para ello no nos debemos olvidar de definir el mtodo const char* what() , presente en todas las clases hijas, y que devuelve un mensaje de error. Esto nos ayuda si queremos ignorar ciertas excepciones. bad_alloc: Es la excepcin que lanza el operador new si no hay sitio en la heap donde declarar el objeto. domain_error: Salta cuando se realizan acciones sin sentido (hacer un pop a una pila vaca). invalid_argument: Es un caso especfico de incumplimiento de una precondicin, el caso en que el argumento pasado no es vlido. En el constructor de esta clase se le pasa el mensaje que luego debe devolver el mtodo what . Un ejemplo sera una temperatura menor que los 0 K. logic_error: Es la clase base de domain_error y invalid_argument. Comprende todas las situaciones en que el programa salte a causa de una precondicin incumplida.

Para crear una nueva excepcin tambin podemos derivar de logic_error, ya que nuestras excepciones suelen ser por causas de precondiciones. Todas estas excepciones estn declaradas en el archivo <stdexcept>. IMPLEMENTACIN DE WHAT() Imaginemos que queremos hacer una excepcin que nos indique que estamos actuando fuera del rango de un vector, Fuera_Rango . La excepcin deber incorporar tanto los extremos del vector como la posicin a la que se quiso acceder y el mtodo what() debe ser capaz de informar de ellos. Sin embargo, el pasar un entero a un char * nos puede dar problemas, ya que no es automtico como en Java. Lo que haremos ser usar una clase llamada ostringstream que convierte en cadenas de caracteres todo aquello que recibe. De esta manera podra mos hacer: const char* what(){ ostringstream mensaje; mensaje << La posicin << posicin << queda fuera del rango [ << minimo << , << maximo <<]; return mensaje.c_str(); } La clase ostringstream est incluida en el fichero <sstream> .

LANZAMIENTO DE EXCEPCIONES La excepcin se lanza mediante la palabra reservada throw. Se producen 3 efectos al usar dicha palabra: Se identifica el tipo de excepcin. Se transfiere la informacin al manejador. Se transfiere el flujo de ejecucin al manejador.

C++ nos permite lanzar cualquier tipo de datos, no solo clases. De esa manera sera valido: int pos, min, max; Fuera_Rango f(pos, min, max); throw f; throw pos; throw new Fuera_Rango(pos, min, max); throw Fuera_Rango(pos, min, max); Lo dicho a la hora del paso de parmetros o la devolucin mediante el return es vlido tambin en el throw. En el primer throw se lanzara una copia de f, por lo que el constructor de copia debe estar presente en la parte pblica. El tercer throw equivaldra a un paso de parmetros por direccin. Sin embargo, con el tercer throw, luego deberamos efectuar un delete para liberar la memoria. En Java no hace falta ya que al terminar el manejador se pierde la referencia y el recolector de basura hace su trabajo. Otra diferencia con Java es que con dicho lenguaje hay que indicar que un mtodo puede lanzar una excepcin. Para ello se usa la palabra reservada throws en la signatura del mtodo. En C++, sin embargo, no hace falta especificarlo. Por defecto, todo puede lanzar excepciones. CAPTURA DE EXCEPCIONES La captura y el manejo de las excepciones se lleva a cabo mediante las palabras try y catch. De esta manera, cuando en un algoritmo pueden surgir errores puedo decidir manejarlos o no. Sin embargo, en caso de no manejarlos, se invoca al manejador por defecto, la funcin terminate, que mata el programa. Esta funcin la podemos modificar mediante la funcin setterminate. La forma de usar try y catch es: try{ } catch( [const] T [&]){ } catch( [const] T *){ } En el primer caso se controlara una excepcin de tipo T o derivados (si utilizamos la referencia). En el segundo caso se entra si lo que se lanza es un puntero a un tipo T o derivados. Adicionalmente se puede declarar un catch genrico, que capturar toda exc epcin que no se haya capturado antes. La sentencia es:

catch(){ } Haremos ahora un programa en que declararemos un simple vector. int main(){ int, desde, hasta, n; cin >> desde >> hasta >> n; Vector v(desde, hasta); for(int i = desde; i <= hasta; i++){ v[i] = i; } V[n] = n; cout << v << endl; } En este programa sencillo ya tenemos dos puntos conflictivos. En primer lugar, el constructor puede dar un error si desde es mayor que hasta. Habra que capturar una excepcin de RangoNoValido. En segundo lugar, n puede quedar fuera del rango del vector, por lo que habra que capturar una excepcin FueraRango. Deberamos hacer: try{ } catch (const RangoNoValido &e){ cerr<<e.watch(); } catch (const FueraRango &e){ cerr<<e.watch(); throw e; } catch (){ cerr<<Error desconocido.; throw; } Mediante el throw; relanzamos la excepcin. Su mayor utilidad es en aquellos casos en que la excepcin la declaramos muda, es decir, sin nombre. Eso puede ocurrir tanto en el ltimo match como en un match en que no definamos el nombre: catch (const FueraRango& ) . LIBERACIN DE RECURSOS En el ejemplo que vimos, todos los datos estaban declarados en la pila, por lo que al hacer el throw la pila se liberaba. Sin embargo, que ocurre si tenemos datos en la heap ? y si tenemos algn fichero abierto? En Java usaramos la sentencia Finally, pero en C++ eso no existe, por lo que debemos usar el ingenio. Tendramos la situacin actual, en la que no controlamos los errores:

void recursos1(){ FILE *f open(prueba.txt, w); Procesar(); //Salta la excepcin fclose (f); } Aqu no se maneja el error, por lo que el manejo correspondera a la funcin que llama a esta funcin. Sin embargo el puntero al fichero estara perdido, por lo que el fichero quedara abierto indefinidamente. Tenemos entonces dos caminos: void recursos2(){ FILE *f open(prueba.txt, w); try{ Procesar(); //Salta la excepcin fclose (f); } catch(const logic_error &e){ fclose(f); throw; } } Esta solucin es poco robusta, ya que nada me obliga a controlarlo, y poco reutilizable. La mejor solucin provendra del aprovechamiento de las clases. Haremos una clase que contenga al fichero, de manera que podemos codificar la liberacin de los recursos en sus destructores. Si metemos el objeto en la pila, al hacer el throw la pila se libera, se invoca a los destructores y se liberan todos los recursos. En el caso de los ficheros podemos usar las clases de los streams, que en sus destructores cierran el fichero. En el caso de los punteros necesitamos una clase que en su destructor libere la memoria apuntada por el puntero. Esa clase es la clase auto_ptr, localizada en memory.h . auto_ptr<int> p = new int; NOTIFICACIN DE LANZAMIENTO Java te obliga a indicar las excepciones que cada mtodo lanza. Como dijimos, C++ da ms libertad y te permite no hacerlo. Sin embargo, hay una ampliacin que te permite indicar las excepciones lanzadas por un mtodo o funcin. Esta indicacin se hace tanto en la declaracin como en la definicin. void f1(){ } void f2() throw (int, logic_error){ } void f3()throw (){ }

La primera funcin puede lanzar cualquier cosa. La segunda tan solo podr lanzar int o logia_error y sus clases derivadas. La tercera funcin no podr lanzar nada. Si lanza algo no dar un error de compilacin, pero en tiempo de ejecucin cascar. Al no esperarse la excepcin se llamar a la funcin unexpected que, por omisin, llamar a la funcin terminate que, por omisin, finaliza la ejecucin. Ambas funciones se pueden especificar con setunexpected y setterminate. Esta notificacin aporta robustez al cdigo, ya que obliga a manejar las excepciones declaradas, aparte de que otorga mayor claridad en el cdigo. Sin embargo, no todos los compiladores aceptan esta funcionalidad. As, el Vis ual Studio .NET 2003 no tiene implementada esta funcionalidad, por lo que ese cdigo es ignorado. En cambio, en el gcc y en la mayora de compiladores GNU s que est contemplada esta opcin. NOTIFICACIN Y POLIMORFISMO Imaginemos: class A{ virtual void m() throw (a, b, c){} }; Class B: public A{ Virtual void m() throw( ?? ){} }; El mtodo m de la clase B solo puede lanzar un subconjunto de las excepciones que puede lanzar el mtodo m de la clase A. Esto es lgico, ya que, al llamar de manera polimrfica al mtodo m, el sistema se espera que lance alguna de las excepciones declaradas por el mtodo de la clase base. Si el mtodo m de la clase A no pudiera lanzar nada, el mtodo m de la clase B no podra lanzar nada. GENERICIDAD La genericidad nos permite crear algoritmos abstrayndonos del tipo de datos. En C++ esto lo podemos hacer mediante las plantillas o templates. template <Typename T> As, a la hora de codificar el algoritmo crearemos unos tipos genricos y luego los usaremos. template <Typename T> void Intercambia( T &a, T &b){ T aux = a; a = b; b = aux; } Sin embargo, observamos que el tipo T debe cumplir ciertos requisitos: Tener implementado el operador de asignacin. Tener implementado el constructor de copia.

Lo que hace el sistema es crear una instancia de la funcin cada vez que se llame con un tipo distinto. Por ello, si intercambiamos dos vectores, los usamos como argumentos en la funcin y nos da algn error, el error lo dar la funcin Intercambia<Vector>. Adems, no podemos olvidarnos de que el compilador no puede adivinar el tipo que usaremos en la funcin. Por eso: const T &maximo(const T& a, const T& b){ return ((a>b)?a:b); } int main(){ int a, b; cout << maximo (a,b); Entero e1(1), e2(2); cout <<maximo(e1,e2).getEntero();//Da error de compilacin } No podemos invocar al mtodo getEntero , aunque sepamos que tendremos un Entero . Los tipos genricos se pueden emplear no solo en funciones, sino tambin en clases. CLASE GENRICA ARBOL.H template <typename T> class Arbol<T>; template <typename T> class Nodo{ T valor; Nodo<T> *izq, *der; unsigned elementos; Nodo(cost T &v, unsigned e=1, const Nodo<T> *i = 0, const Nodo<T> *d = 0){ valor = v; elementos = e; izq = i; der = d; } friend class Arbol; void insertar (const T& v); void inorden (ostream &o) const; friend ostream &operator<< <T> (ostream &o, const Arbol<T> &a); }; template <typename T> class Arbol{ Nodo<T> *raiz;

static void destruir(Nodo<T> *n); static Nodo<T> *copiar(const Nodo<T> *a); public: Arbol()<T>{ raiz = 0;} ~Arbol()<T>{if (raiz) destruir(raiz);} Arbol<T>( const Arbol<T> &a); Arbol<T> &operator =(const Arbol<T> &a); void insertar(const T& v){ if (raiz) raiz->insertar(v); else raiz = new Nodo<T> (v); } }; template <typename T> ostream &operator<<(ostream &, const Arbol<T> &); ARBOL.CPP template <typename T> void Nodo<T>::insertar(const T& v){ if (v == valor){ elementos++; return; } if (valor > v){ if (izq) izq->insertar(v); else izq = new Nodo<T>(v); }else{ if (der) der->insertar(v); else der = new Nodo<T>(v); } } template <typename T> void Nodo<T>:: inorden (ostream &o) const{ if(izq) izq->inorden(o); o << ( << valor << : << elementos << ); if(der) der->inorden(o) }

template <typename T> Arbol<T>:: Arbol<T>( const Arbol<T> &a){ raiz = copiar(a.raiz); } template <typename T> Arbol<T>::Arbol<T> &operator =(const Arbol<T> &a){ If (this != a){//Si no, al liberar recursos nos lo cargaremos delete raiz; raiz = copiar(a.raiz); } return *this; } template <typename T> void Arbol<T>:: destruir(Nodo<T> *n){ destruir(n->izq); destruir(n->der); delete n; } template <typename T> Nodo<T> *Arbol<T>:: copiar(const Nodo<T> *a){ if (!a) return 0; return new Nodo<T>(a->valor, a->elementos, copiar(a->izq), copiar(a->der)); } template <typename T> ostream &operator <<(ostream &o, const Arbol<T> &a){ if (a.raiz) a.raiz->inorden(o); return o; } Podemos entonces crear un rbol de String, de int, o de cualquier tipo que cumpla los requisitos necesarios (tener operador de asignacin) Arbol<string> as; Arbol<int> ai; Arbol<Vector> av;

LIBRERA ESTNDAR La Librera Estndar nos ofrece una gran gama de funcionalidades que hace ms sencillo escribir nuestros programas y controlar los errores. Una importante parte de ella es l a STL (Standard Template Library), un subconjunto de la librera estndar que ofrece un conjunto de colecciones y algoritmos para tratar las listas. Aparte de la STL hay otras partes en la librera Estndar, como la referente a las cadenas de caracteres (string), la gestin de nmeros complejos (complex), los lmites de los tipos simples (numeric_limits), la gestin de flujos (streams) o la internacionalizacin (locale). LA STL La STL, a su vez, se divide en tres grandes apartados: STL -- CONTENEDORES Los contenedores nos ofrecen implementaciones de colecciones de elementos, como listas, vectores Estos contenedores son de varios tipos: Secuenciales: Sus elementos no estn ordenados: o list o vector o deque: Contenedor en el que se pueden extraer e insertar elemento por el principio y por el final. Asociativos: Sus elementos estn ordenados. Por defecto siguen un orden descendente. o map: Una especie de tabla hash. A partir de una clave se obtiene un elemento. o multimap: Una especie de tabla hash abierta. A una misma clave se le asocian diversos elementos. o set: Es un conjunto. No se pueden repetir sus elementos. o multiset: Es un conjunto en el que s se pueden repetir elementos. Adaptadores: Son contenedores hechos a partir de otros. o snack : Es el equivalente a una pila. o queue : Como su nombre indica, es una cola. o priority_queue: Es una cola de prioridad. o bit_set: Es un conjunto de bits. Se suele usar con mscaras. o valarray: se trata de un vector algebraico.

A pesar de la gran variedad todos cumplen ciertas caractersticas, como es el hecho de que trabajen con copias de los elementos insertados (si queremos que sobrevivan al contenedor hemos de usar punteros), acepten iteradotes y que, generalmente, no se lancen excepciones. Ya que trabajan con copias, los elementos que insertemos tienen que cumplir ciertas caractersticas tambin, como la disponibilidad del constructor de copia y el operador de asignacin, as como la posibilidad de destruir los objetos. Adems, otros contenedores exigen que exista el constructor sin parmetros, que se haya definido el operador == y el operador <. Esto lo usan los contenedores asociativos. La redefinicin del operador == y el operador < nos puede traer problemas si estamos trabajando con punteros, ya que comparar las direcciones. Por ello tendremos que redefinir esos operadores para el caso en que se trate de punteros: int operator<(const Cronometro *op1, const Cronometro *op2){}

STL -- ITERADORES Se trata de una abstraccin que nos permite iterar por los elementos de una coleccin. Un objeto iterador representa una posicin dentro de un contenedor, de manera que podemos consultar el elemento situado en dicha posicin. Los iteradores poseen ciertos operadores y mtodos: operator* : nos permite acceder al elemento sealado por el iterador. operator++ : avanza al siguiente elemento; operator-> : pasa un mensaje al elemento sealado. operator== : compara dos iteradotes y devuelve trae si ambos apuntan a la misma direccin de memoria. operator!= : devolver trae si apuntan a distinta direccin. operator= As mismo los contenedores nos ofrecen mtodos que nos permitirn obtener iteradotes; begin : devuelve un iterador al primer elemento. end: devuelve un iterador que apunta a la posicin siguiente al ltimo elemento. Se suele usar para comprobar si un iterador ya ha alcanzado el final. rbegin: Devuelve un iterador al ltimo elemento de la lista. rbegin corresponde a Reverse Begin, es decir, el principio de la lista al revs. rend: Devuelve un iterador que seala a la posicin anterior al primer elemento. rend corresponde a Reverse End, es decir, el mismo comportamiento que end pero en la lista al revs. Adems, cada contenedor posee dos tipos de iteradores, iterator y const_iterator. STL -- ALGORITMOS La STL adems incorpora distintos algoritmos para trabajar con estos contenedores. Los algoritmos son funciones globales, fuertemente sobrecargadas, que, en realidad, trabajan con iteradores. De esta manera consiguen cierta independencia con respecto al contenedor. Como ya se dijo antes, estas funciones apenas comprueban precondiciones o lanzan excepciones, por motivos de eficiencia. Por ello, el programador debe comprobar lo que se le pasa a cada funcin. Tenemos as: find: Encuentra elementos. copy: Copia elementos. fill: Rellena un contenedor. sort: Ordena un contenedor. replace: Reemplaza elementos. Muchos de estos algoritmos se encuentran en <algorithm> , pero otros ms matemticos se pueden encontrar en <numeric>.

Potrebbero piacerti anche