1.26k likes | 1.5k Views
Algoritmos y Estructuras de Da tos. Introducción al C++. Paradigmas de la Programación. Programación procedural Enfatiza el aspecto algorítmico Programa = Algoritmos + Datos. La programación procedural en lenguajes previos al C ( BASIC ,FORTAN ) tenían problemas organizacionales
E N D
Algoritmos y Estructuras de Datos Introducción al C++
Paradigmas de la Programación Programación procedural Enfatiza el aspecto algorítmico Programa = Algoritmos + Datos
La programación procedural en lenguajes previos al C ( BASIC ,FORTAN ) tenían problemas organizacionales debido a que se usaban sentencias de salto (GO TO) que modificaban el flujo normal del programa haciéndolos muy difíciles de seguir. Paradigmas de la Programación -Programación spaghetti -Write-only code
1965 2000 Paradigmas de la Programación EXPECT PARADIGM SHIFTS En respuesta a este problema se crearon lenguajes que definían un estilo mas disciplinado llamado programación estructurada. En este caso los saltos se limitan a construcciones o estructuras perfectamente definidas (while ,do while ,for y el if-else)
Paradigmas de la Programación Otro principio que también se incorporo es el llamado TOP-DOWN design. La idea básica detrás de este principio era dividir un problema grande en mas pequeños. Hasta terminar en programas mas pequeños llamados módulos. C tiene soporte para este paradigma mediante unidades de programación llamadas funciones
Paradigmas de la Programación En la programación modular se puso énfasis en la organización de los datos mas que en el aspecto algorítmico del programa. En este caso el paradigma era : “Decida que modulos necesita y particione el programa de manera que los datos queden ocultos en dichos modulos”
Paradigmas de la Programación Ej STACK : 1- Crear una interfase para el usuario por ej: push() y pop() . 2- Asegurarse que la implementación del stack (pej: un array de elementos) puede accederse solo a través de dicha interfase. 3- Asegurarse que el stack sea inicializado antes de su primeruso. 4- Proveer un mecanismo para la destrucción del stack cuando no se mas usado.
Ej.: STACK La interfase del stack puede ser definida en stack.h como : Paradigmas de la Programacion // User Interface void push(char); char pop(); const int stack_size =100;
La implementación del stack puede ser definida en stack.c como : Paradigmas de la Programación // Stack implementation #include “stack.h” static char v[stack_size]; (static LOCAL!!!!) static char *p=v; // Stack inicialmente vacío push(char c) { .......} // si hay lugar guardo un elemento char pop(){........} // si no esta vacío saco un elemento
Paradigmas de la Programacion La programación modular lleva a la centralización de datos relacionados con un tipo dado, lo que es una notable mejora respecto de los paradigmas previos. El problema es que para cada tipo se debe crear un mecanismo distinto para crear una ”variable “ . Ademas estas “variables” no gozan de los mecanismos que tienen los tipos incorporados en el lenguaje (int, float,etc) como ser la visibillidad (scope) la duracion, pasaje de argumentos ,etc.
Paradigmas de la Programacion Lenguajes como el C++ y ADA permiten al usuario definir tipos que se comportan casi de la misma forma que los tipos nativos del lenguaje. Estos tipos son también conocidos como ADT (Abstract Data Types) o directamente tipos definidos por el usuario El paradigma ahora se convierte en: “Decida que tipo de datos necesita y provea un conjunto completo de operaciones para dicho tipo”
Paradigmas de la Programacion Los números complejos son un ejemplo de tipo definido por el usuario. En el caso del numero complejo debemos definir entre otros: Como se crea un complejo Las operaciones que queremos realizar sobre ellos La destrucción del mismo
Primera Clase !!! En el caso de los números complejos se puede usar una struct para definirlo. De esta forma uno podría crear destruir y manipular complejos. Es decir que uno podría modelar OBJETOS usando estructuras y escribir código que opere sobre ellas . Es esto lenguaje orientado a objetos. ? Desafortunadamente no pues el paradigma (OOP) brinda soporte adicional como ser la encapsulacion,el polimorfismo y la herencia que no son aplicables a struct. El C++ brinda algo que se comporta de la misma manera que una struct pero que tiene propiedades adicionales y es conocida como Clase.
Primera Clase !!! La clase Ccomplejo se comporta igual que la estructura Complejo typedef struct { double x; double y; }Complejo; Class Ccomplejo { public: double m_x; double m_y; }; Data members
Clases Una clase es un tipo definido por el usuario Así como una estructura es un molde así lo es una clase De la misma forma que creamos una variable usando estructuras lo hacemos con las clases. Cuando creamos una variable de una clase dada decimos que creamos un objeto o que creamos una instancia de la clase Ccomplejo z1; // z1 es un objeto tipo Ccomplejo (declaración) Los datos miembros pueden ser cualquiera de los tipos conocidos (float,int, arrays) o tipos definidos por el usuario Además de los datos la clase puede contener funciones que operan sobre estos últimos a estas funciones se las conoce como funciones miembro Los nombres usados dentro de una clase son locales a ella.
Clases:Control de acceso Class Ccomplejo { double m_x; public: double m_y; }; Public: Acceso total. Private: No se pueden acceder desde fuera de la clase Protected: Se vera mas adelante Por omision todos los miembros son privados. Ccomplejo z1; z1.m_x =3; Invalido z1.m_y =3; OK
class CBox // Class definition at global scope { public: double m_Length; // Length of a box in inches double m_Breadth; // Breadth of a box in inches double m_Height; // Height of a box in inches }; int main(void) { CBox Box1; // Declare Box1 of type CBox CBox Box2; // Declare Box2 of type CBox } Clases:Ejemplo Creo 2 instancias de CBox
int main(void) { CBox Box1; // Declare Box1 of type CBox CBox Box2; // Declare Box2 of type CBox double volume = 0.0; // Store the volume of a box here Box1.m_Height = 18.0; // Define the values Box1.m_Length = 78.0; // of the members of Box1.m_Breadth = 24.0; // the object Box1 Box2.m_Height = Box1.m_Height - 10; // Define Box2 Box2.m_Length = Box1.m_Length/2.0; // members in Box2.m_Breadth = 0.25*Box1.m_Length; // terms of Box1 Clases:Ejemplo
class CBox // Class definition at global scope { public: double m_Length; // Length of a box in inches double m_Breadth; // Breadth of a box in inches double m_Height; // Height of a box in inches double Volume(void) { return m_Length*m_Breadth*m_Height; } }; Clases:Funciones miembro
El acceso a las funciones miembro se realiza de la misma forma que cuando se accede a un dato miembro Clases:Funciones miembro double volume = 0.0; volume = Box1.Volume(); // Calculate volume of Box1 cout << endl << "Volume of Box1 = " << volume; cout << endl << "Volume of Box2 = " << Box2.Volume(); No se puede invocar a una función miembro sin especificar el nombre del objeto
No es necesario definir la funcion dentro de la clase se puede definir afuera.Solo basta con definir el prototipo de la funcion dentro de la clase y la definicion fuera de la misma. Clases:Funciones miembro class CBox // Class definition at global scope { public: double m_Length; // Length of a box in inches double m_Breadth; // Breadth of a box in inches double m_Height; // Height of a box in inches double Volume(void); // Prototipo de la funcion };
double Cbox::Volume(void) { return m_Length*m_Breadth*m_Height; } Clases:Funciones miembro El operador :: (operador resolución de entorno) le indica al compilador a quien pertenece la función. No existe ninguna diferencia en cuanto al comportamiento del programa si en como el compilador trata a la función.
Cuando una función es declarada “inline” el compilador reemplaza su llamada por su definicion. Toda función definida dentro de una clase es inline por defecto (declaración inline implícita). Una declaración explicita seria: Clases: Funciones “Inline” Inlinedouble Cbox::Volume(void) { return m_Length*m_Breadth*m_Height; }
Cuando se tienen varias clases y con muchos miembros datos la inicialización de los mismos puede ser engorrosa y peligrosa pues se pueden omitir algunas de ellas. Además los datos miembros privados no son accesibles desde el exterior. C++ provee una función implícita en cada clase que se llama constructor por defecto. Esta función es invocada cada vez que se crea un objeto. El nombre de esta función es el mismo que la clase. Clases: Constructores Ccomplex( ) { }
Uno puede definir su propio constructor para inicializar los miembros de datos dentro de la clase este constructor es explicito. Clases: Constructores CBox(double lv, double bv, double hv) // Constructor definition { cout << endl << "Constructor called."; m_Length = lv; // Set values of m_Breadth = bv; // data members m_Height = hv; }
En el siguiente ejemplo se crean 3 objetos dos de ellos están inicializados. Clases:Constructores CBox Box1(78.0,24.0,18.0); // Declare and initialize Box1 of type CBox CBox Box2; // Declare Box2 - no initial values CBox CigarBox(8.0,5.0,1.0);
Cuando el usuario define un constructor el constructor por defecto desaparece pues se asume que el usuario provee todos los constructores necesarios. Por lo tanto al compilar el programa con la definición previa se genera un error pues no encuentra el constructor por defecto para el segundo objeto. Es el usuario quien debe proveerlo En el siguiente ejemplo se agrego el constructor por defecto. Clases: Constructores CBox(double lv, double bv, double hv) // Constructor definition { cout << endl << "Constructor called."; m_Length = lv; // Set values of m_Breadth = bv; // data members m_Height = hv; } // Default constructor definition CBox( ) { cout << endl << "Default constructor called."; }
Algo que puede llamar la atención es que tenemos dos funciones constructoras con el mismo nombre. Como sabe el compilador a cual invocar? Simplemente se fija en el tipo y numero de argumentos con que se invoca a la función. Esto se conoce con el nombre de sobrecarga de funciones( función overloading) porejemplo supongamos que necesitamos una función que encuentre el máximo de dos valores. Esos valores pueden ser double o int . El nombre mas apropiado para la función seria max(valor1 ,valor2). En C tendríamos que usar una ADT o nombres diferentes pej.: maxint , maxdouble etc.. Con la sobrecarga de funciones podemos evitarnos este problema pues es el compìlador quien toma la decisión de cual usar. Ej: Sobrecargando Funciones int max(int a , int b) { ………. } double max(double a , double b) { …….. }
Ejemplo: #include<iostream.h> int max(int,int); double max(double,double); main() { double a; a=max(10,11); cout << a << endl; a=max(10.1,11.2); cout << a << endl; } int max(int a,int b) { cout << endl << "Int max="; return((a>b)?a:b); } double max(double a,double b) { cout << endl << "Flt max="; return((a>b)?a:b); } Sobrecargando Funciones Int max=11 Flt max=11.2
En otras palabras la sobrecarga de funciones nos permite re-usar un nombre independientemente del tipo que de los operadnos. Encontrar el máximo de dos valores es una operación muy común para muchos tipos de datos como ser floats doubles ints ,etc.,… Un problema al que uno se enfenta es que el codigo de la funcion para cada tipo de dato se repite. Por ejemplo el codigo para encontrar el maximo de enteros y floats es exactamente igual la unica diferencia es el tipo. C++ provee una solucion para este problema que se conoce como moldes para funciones (function templates). Sobrecargando Funciones
template <classAny > void swap (Any &a , Any &b) { Any temp; temp=a; a=b; b=temp; } Templates para Funciones La primera línea sirve para que el compilador sepa que estamos preparando un molde y que el tipo genérico se llamara Any (muchos libros usan T o T1 ). Este molde NO genera código solo es una directiva para que el compilador sepa como definir la función. Cuando invocamos a la función swap el compilador se fija en los tipos que usamos y crea la funcion correspondiente. Por ejemplo si usamos enteros en la linea de argumentos el compilador reemplazara Any por int.
Los templates pueden tener mas de un tipo genérico template <class Any1,class Any2 > Templates para Funciones Y también se pueden sobrecargar!!! template <class Any> // swap int, floats,etc void swap(Any &a, Any &b); template <class Any> void swap(Any *a, Any *b, int arraysize); // swap array elements 1 1 Se deja al alumno como ejercicio la implementación de esta función.
Ejemplo #include<iostream.h> template <class T> T max(T a,T b); // Prototipo main() { double a; a=max(14,11); cout << endl << a; a=max(15.1,11.3); cout << endl << a; return(0); } template <class T> T max(T a,T b) // Definicion { return((a>b)?a:b); } Templates para Funciones 14 15.1
Se pueden especificar valores por defecto en el caso de los constructores por ejemplo: Valores por defecto para constructores CBox( double lv=1.0, double bv=1.0, double hv=1.0 ) { cout << endl << "Constructor called."; m_Length = lv; // Set values of data members m_Breadth = bv; m_Height = hv; } En este caso debe eliminarse el constructor por defecto pues colisiona con esta ultima definición creando una ambigüedad. En efecto si se crea un objeto como: Cbox CigarrBox; El compilador no puede distinguir entre el constructor por defecto y el ultimo constructor .
Una forma alternativa de hacer lo mismo es mediante lo que se conoce como lista de inicialización: Valores por defecto para constructores CBox (double lv=1.0, double bv=1.0, double hv=1.0) : m_Length (lv), { m_Breadth(bv), m_Height (hv) cout << endl << "Constructor called."; } • Esta forma de inicializar es necesaria en ciertos casos particulares y solo se puede usar para constructores. Estos casos son: • Cuando el miembro dato es const. y no estático. • Cuando el miembro dato es una referencia. Importante: Recordar que los miembros de una clase son inicializados en el orden en que fueron declarados no en el orden en que aparecen en la lista de inicialización.
Clase DatosMiembros Públicos Funciones Miembros Publicas (servidor) Función Externa a la clase (cliente) DatosMiembros Privados Funciones Miembros Privadas Control de acceso La posibilidad de definirmiembros privadosde una clase nos permite separar la implementación de la misma desu interfase. Miembros privados de la clase
class CBox // Class definition at global scope { public: CBox(double lv=1.0, double bv=1.0, double hv=1.0) // Constructor definition { cout << endl << "Constructor called."; m_Length = lv; // Set values of m_Breadth = bv; // data members m_Height = hv; Servicio } double Volume() // Function to calculate the volume of a box { return m_Length*m_Breadth*m_Height; } private: double m_Length; // Length of a box in inches double m_Breadth; // Breadth of a box in inches double m_Height; // Height of a box in inches }; Miembros privados de la clase
Con este cambio no se puede acceder a los datos miembros desde afuera. Esto permite el ocultamiento de los mismos protegiéndolos de un acceso inadvertido (y los detalles de la implementación en el caso de las funciones privadas). Ahora la única forma de acceso es a través del constructor o funciones publicas que hacen a la interfase del objeto. Declarar a los miembros datos como privados puede parecer una medida algo extrema pero lo único que queremos es protegerlos de una modificación inadvertida desde el exterior. Esto no nos impide tener acceso de lectura a los mismos si creamos una interfaz publica como la siguiente: Miembros privados de la clase inline double Cbox:: GetLength(void) { return (m_Lenght)} ; len = Box2. GetLength() ;
En algunas ocasiones es necesario que algunas funciones que NO pertenecen a la clase tengan acceso a los miembros de la clase. A este grupo de funciones se las conoce como funciones amigas de la clase. Este grupo de funciones no son miembros de la clase por lo tanto no gozan de todos los privilegios que tienen las funciones miembros. Simplemente son funciones comunes que gozan de ciertos privilegios. Concretamente las funciones amigas pueden acceder a todos los miembros privados de la clase pero no pueden usar directamente el nombre del miembro sino que deben especificar el objeto al que pertenecen de la misma manera que lo haría una función regular excepto salvo que esta ultima no puede acceder a los miembros privados de la clase. Haciendo Amigos
La definición de estas funciones puede o no estar en la clase y en caso de no estarlo debe declararse el prototipo dentro de la misma. La declaración del prototipo debe estar precedida por la palabra friend. Ej.: Haciendo Amigos friend double BoxSufrace (CBox aBox); El posicionamiento dentro de la definición de la clase puede hacerse en cualquier lugar de la misma dado que los atributos de acceso (private o public) no son aplicables a las funciones amigas pues no son miembros de la clase. Sin embargo es aconsejable poner la declaración de las funciones amigas después de todas las declaraciones publicas y privadas. Además si bien la función es de acceso global es aconsejable no poner la definición de la misma dentro de la clase sino solamente el prototipo pues después de todo no es una función miembro y podría complicar la lectura de la definición de la clase.
class CBox // Class definition at global scope { public: CBox(double lv=1.0, double bv=1.0, double hv=1.0) // Constructor definition { cout << endl << "Constructor called."; m_Length = lv; // Set values of m_Breadth = bv; // data members m_Height = hv; } double Volume() // Function to calculate the volume of a box { return m_Length*m_Breadth*m_Height; } private: double m_Length; // Length of a box in inches double m_Breadth; // Breadth of a box in inches double m_Height; // Height of a box in inches friend double BoxSurface(CBox aBox); //Friend function }; Haciendo Amigos
La definición de la función es: // friend function to calculate the surface area of a Box object double BoxSurface(CBox aBox) { return 2.0*(aBox.m_Length*aBox.m_Breadth + aBox.m_Length*aBox.m_Height + aBox.m_Height*aBox.m_Breadth); } Haciendo Amigos Obsérvese que el acceso a los miembros es el mismo que haría una función regular es decir Objeto.miembro. Lo mismo ocurre durante la invocación, no se hace referencia al objeto: cout << endl << "Surface area of Match = " << BoxSurface(Match);
Supongamos que declaramos e inicializamos un objeto perteneciente a la clase CBox de la siguiente manera: Constructores copiadores CBox Box1(78.0, 24.0, 18.0); CBox Box2 = Box1; // Initialize Box2 with Box1 Lo que queremos hacer es crear e inicializar un segundo objeto Box2 a partir del primero. La invocación seria cout << endl << "Box1 volume = " << Box1.Volume() << endl << "Box2 volume = " << Box2.Volume();
El programa hace exactamente lo esperado, el volumen de ambos objetos es el mismo: Constructor called. Box1 volume = 33696 Box2 volume = 33696 Constructores copiadores Pero el constructor fue invocado una sola vez para la creación de Box1. Como fue creado Box2 ? . El mecanismo para la creación del segundo objeto es similar a la que teníamos cuando no definíamos ningún constructor para la clase: el compilador suministraba uno por defecto. En este caso ocurre lo mismo, el compilador suministra un constructor copiador por defecto llamado constructor copiador. Este constructor realiza lo mismo que haríamos nosotros es decir crea el nuevo objeto haciendo una copia miembro a miembro .
Cuando invocamos a la función volume() accedíamos a los miembros de la clase Cbox en forma directa usando sus nombres: double Volume() // Function to calculate the volume of a box { return m_Length*m_Breadth*m_Height; } El puntero “THIS” Todo objeto del tipo Cbox tiene estos miembros pero como se sabe a cual de los objetos creados pertenecen? Pues bien cuando se invoca a una función miembro de un objeto dado dicha función recibe un puntero oculto llamado thisque apunta a dicho objeto. Entonces cuando se escribe: return m_Length*m_Breadth*m_Height; En realidad es: return thism_Length* thism_Breadth* thism_Height;
En este ultimo caso el uso del puntero this fue explicito. Se puede usar por ejemplo este puntero en forma explicita cuando se desea devolver un puntero al objeto actual. Supongamos que deseamos comparar dos objetos del tipo cbox en base a su volumen. Entonces podemos poner: El puntero “THIS” // Function to compare two boxes which returns TRUE (1) // if the first is greater than the second, and FALSE (0) otherwise int compare(CBox xBox) { return this->Volume() > xBox.Volume(); } if( Cigar.compare(Match) )
El main completo seria: int main(void) { CBox Match(2.2, 1.1, 0.5); // Declare Match box CBox Cigar(8.0, 5.0 ,1.0); // Declare Cigar box if(Cigar.compare(Match)) cout << endl << "Match is smaller than Cigar"; else cout << endl << "Match is equal to or larger than Cigar"; cout << endl; return 0; } El puntero “THIS”
Nótese que la función compare podría haberse escrito haciendo uso implícito del puntero this : int compare(CBox xBox) { return Volume( ) > xBox.Volume( ); } El puntero “THIS” Si la función compare hubiese sido externa no tendría acceso al puntero this y por lo tanto habría que escribirla como: int compare(CBox Box1,CBox Box2) { return Box1.volume( ) > Box2. volume( ) }
Podemos declarar arrays de objetos de la misma manera que lo hacemos con los tipos nativos (float, int, etc.). Cada elemento del array provocara una llamada al constructor por defecto. Arrays de objetos // Constructor definition CBox(double lv, double bv=1.0, double hv=1.0) { cout << endl << "Constructor called."; m_Length = lv; // Set values of m_Breadth = bv; // data members m_Height = hv; } // Default constructor CBox() { cout << endl << "Default constructor called."; m_Length = m_Breadth = m_Height = 1.0; }
Supongamos que creamos un array de cinco cajas y una caja de cigarros. int main(void) { CBox Boxes[5]; // Array of CBox objects declared CBox Cigar(8.0, 5.0 ,1.0); // Declare Cigar box cout << endl << "Volume of Boxes[3] = " << Boxes[3].Volume() << endl << "Volume of Cigar = " << Cigar.Volume(); cout << endl; return 0; } Arrays de objetos