Apuntes de Estructuras de Datos Matias Valdenegro T. 2 de marzo de 2006 2 Índice general 1. Introduccion a C++ 1.1. Diferencias con C . . . . . . . 1.2. Nuevos Tipos de Datos . . . . 1.3. Manejo de Memoria Dinamica 1.4. Flujos . . . . . . . . . . . . . 1.5. Sobrecarga de Funciones . . . 1.6. Parametros por Defecto . . . 1.7. Plantillas . . . . . . . . . . . 1.8. Referencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 5 5 5 6 7 7 8 9 2. Programacion Orientada a Objetos 11 2.1. Fundamentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.2. Implementacion en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.3. Plantillas de Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 3 ÍNDICE GENERAL ÍNDICE GENERAL 4 Capı́tulo 1 Introduccion a C++ 1.1. Diferencias con C C++ podria considerarse una version mas moderna y mejorada de C++, ya que este ultimo contiene un conjunto de ayudas a la programacion que C no tiene. Entre las diferencias y semejanzas mas importantes, estan : El codigo en C se puede compilar con el Compilador de C++. Se pueden llamar funciones escritas en C, desde C++. Los tipos de datos se mantienen, y se agregan el tipo de dato bool. C++ tiene un nuevo sistema de memoria dinamica, que es mas facil de usar. C++ permite el uso de Tecnicas de Programacion Orientada a Objetos (POO) a travez del uso de clases. 1.2. Nuevos Tipos de Datos Normalmente en C, si uno deseaba retornar un valor de exito o fracaso en alguna funcion, generalmente se declaraba dicha funcion con tipo de retorno int y se retornaba el valor 1 en el caso de exito, y 0 en el caso de un fracaso. En C++, esto ya no es necesario, ya que el lenguaje incorpora el tipo de dato bool, que corresponde a una variable de valor booleano, con valores true y false, lo cual facilita la lectura del codigo. 1.3. Manejo de Memoria Dinamica En C++ el manejo de Memoria Dinamica, es basicamente el mismo, pero tiene ciertas diferencias que lo hacen de mas facil uso. Entre estas diferencias, tenemos : Las peticiones de memoria dinamica se realizaran usando el operador new y liberaremos memoria usando el operador delete. No necesitaremos calcular los tamanos de las estructuras de datos, ya que el compilador realizara esto automaticamente. El formato del uso de los operadores new y delete es : 5 1.4. FLUJOS CAPÍTULO 1. INTRODUCCION A C++ Tipo ∗ p u n t e r o = new Tipo ; delete p u n t e r o ; Tipo ∗ p u n t e r o = new Tipo [ 1 0 ] ; delete [ ] p u n t e r o ; A diferencia del uso de malloc() y free(), no es necesario especificar el tamano de los tipos de datos, y ademas se usa el mismo operador para solicitar un trozo de memoria, como para solicitar un arreglo de trozos de memoria. La unica diferencia es que el operador delete requiere unos corchetes a continuacion de este en el caso de liberar memoria que corresponde a un arreglo, de lo contrario se producen problemas y los llamados ”Memory Leaks”. 1.4. Flujos En C, nosotros usamos la funcion printf() para imprimir mensajes hacia la consola o interfaz de modo texto del sistema operativo. En C++, esta funcion todavia se puede usar, pero existe una mejor forma, mas facil de usar, de imprimir mensajes en pantalla. Un flujo es una abstraccion de la comunicacion entre el teclado y la pantalla, ya que estos se comunican con flujos o ”streams” de caracteres que van en una cierta direccion, y el dispositivo correspondiente interpreta. Todo eso es teoria, en la practica, esto se refleja en que C++ contiene dos nuevos operadores, llamados operadores de insercion y extraccion, y corresponden a ”((” y ”))”, dichos operadores se pueden usar para insertar ((() un dato en un cierto flujo, con lo que imprimimos mensajes en pantalla. ))Equivalentemente, con el operador )) se extraen datos de un flujo, con lo que ingresamos datos desde el teclado. Todos estos operadores se pueden usar con cualquier tipo de dato, y no requieren la memorizacion de ciertos codigos como era necesario con prinf y scanf. El flujo de entrada se llama cin y el de salida se llama cout, y son simples variables con las que podemos usar los operadores (( y )). Suficiente teoria, vamos a los ejemplos: #include <i o s t r e a m > int numero = 9 5 ; f l o a t otronumero = 3 . 1 4 1 5 ; c o u t << ” Mensaje ” ; c o u t << ” Mensaje ” << numero << otronumero ; /∗ Imprime ” Mensaje ” en l a P a n t a l l a ∗/ /∗ Tambien podemos c o n c a t e n a r v a r i o s ∗/ /∗ f l u j o s en l a misma d i r e c c i o n . ∗/ int e n t r a d a ; float otraentrada ; c i n >> e n t r a d a ; c i n >> o t r a e n t r a d a ; /∗ Entrada por t e c l a d o de un numero e n t e r o ∗/ /∗ Entrada por t e c l a d o de un numero r e a l . ∗/ int a , b , c ; c i n >> a >> b >> c ; /∗ Tambien podemos c o n c a t e n a r . ∗/ 6 CAPÍTULO 1. INTRODUCCION A C++ 1.5. 1.5. SOBRECARGA DE FUNCIONES Sobrecarga de Funciones En C, si declaramos dos funciones con el mismo nombre, aunque estan usen parametros diferentes, se produce un error de compilacion, ya que el compilador de C no puede diferenciarlas. Esto en muchos casos representa una desventaja, ya que se necesitan usar funciones que operen en diferentes tipos de datos, pero que hagan la misma operacion. En C++, podemos usar la caracteristica de poder Sobrecargar funciones, y que corresponde a declarar y usar funciones con el mismo nombre, pero con diferente numero, tipo y orden de parametros en su declaracion y uso. Podemos demostrarlo con el siguiente ejemplo : int f u n c i o n ( int a ) { return a ∗ a ; } float funcion ( float a) { return a ∗ a ; } int a = f u n c i o n ( 2 ) ; float b = funcion ( 4 . 0 ) ; En este caso, el compilador selecciono automaticamente la primera funcion al llamarla con el parametro entero 2, y en la segunda llamada, selecciono la segunda funcion al ser llamada con el parametro float. Que sucede si llamamos la funcion con otros parametros? Se producira un error en el momento de compilacion, indicandonos las versiones sobrecargadas de la funcion que podremos usar. Lo mismo ocurrira si llamamos la funcion con parametros en el orden incorrecto, o si la llamada de esta es ambigua. La ambiguedad se produce cuando mas de una funcion puede ser llamada dado los parametros que se les han pasado a esta. Esto solo deberia ocurrir cuando se usen Clases con Herencia, lo que se estudiara mas adelante. 1.6. Parametros por Defecto Nuevamente en el topico de las funciones, podemos hacer que estas tengan valores ”por defecto” para sus parametros, en el caso de que estos no esten especificados, creando la posibilidad de llamar la funcion. Mejor pongamos un ejemplo : float funcion ( float a = 0.0) { return a / 2 . 0 + a ∗ 2 . 0 ; } float x = funcion ( ) ; /∗ Asume a = 0 . 0 ∗/ f l o a t y = f u n c i o n ( 2 . 0 ) ; /∗ Asume a = 2 . 0 ∗/ Como se ve en el ejemplo, la funcion que hemos definido toma solo un parametro, que tiene un valor por defecto de 0.0, y por ende podemos llamar la funcion sin parametros, y inmediatamente el parametro a tomara el valor por defecto que es 0.0, y en caso contrario se usara el valor del parametro que se especifique. Multiples parametros por defecto estan permitidos, pero tienen el detalle de que estos deben aparecer al final de la lista de parametros de la funcion, ya que de lo contrario, seria imposible especificar ciertos parametros sin tener 7 1.7. PLANTILLAS CAPÍTULO 1. INTRODUCCION A C++ que especificar otros. Por ejemplo : void f u n c i o n ( int a , char ∗ s t r i n g = ” Hola ” , double numero = 1 . 0 ) { // Codigo de l a f u n c i o n . } funcion ( 3 ) ; f u n c i o n ( 3 , ”Chao” ) ; f u n c i o n ( 3 , ”Chao” , 1 . 0 ) ; /∗ Llamada v a l i d a . ∗/ /∗ Llamada v a l i d a . ∗/ /∗ Llamada v a l i d a . ∗/ void o t r a f u n c i o n ( int a , int b = 0 , f l o a t c ) { // Codigo de l a f u n c i o n . } otrafuncion (1 , 2 , 3 . 0 ) ; otrafuncion (1); otrafuncion (1 , 1 . 0 ) ; 1.7. /∗ Unica l l a m a d a v a l i d a . ∗/ /∗ Llamada no v a l i d a . ∗/ /∗ Llamada no v a l i d a . ∗/ Plantillas Las Plantillas se aplican a funciones y a Clases, por el momento solo veremos Plantillas de Funciones. Plantillas de Clases se vera en el Capitulo de Programacion Orientada a Objetos. La razon de ser de las Plantillas, es soportar la llamada Programacion Generica, termino que corresponde a crear funciones que toman tipos de datos genericos, de modo que estas funciones puedan ser usadas con distintos tipos de datos diferentes, sin necesidad de reescribir el codigo. La declaracion y implementacion de una funcion Plantilla, es la siguiente: template <c l a s s T> t i p o −r e t o r n o nombrefuncion ( pa r a m e t ro s ) { /∗ Codigo de l a Funcion , puede u s a r T como e l nombre d e l t i p o g e n e r i c o . ∗/ } En el caso de requerir una Funcion Plantilla que requiera mas de un tipo generico, la declaracion es : template <c l a s s A, c l a s s B, c l a s s C> t i p o −r e t o r n o nombrefuncion ( pa r a m e t ro s ) { /∗ Codigo de l a Funcion , puede u s a r A, B o C como e l nombre de l o s t i p o s g e n e r i c o s . ∗/ } Obviamente, el nombre del Tipo Generico no debe ser necesariamente T, A, B o C, y puede ser cualquier combinacion de caracteres. Un muy buen ejemplo del uso de Funciones Plantillas, son los algoritmos de Ordenacion que se vieron en el curso de Programacion: void i n t e r c a m b i a r ( int a [ ] , int i , int j ) { int temp = a [ i ] ; a[ i ] = a[ j ]; 8 CAPÍTULO 1. INTRODUCCION A C++ 1.8. REFERENCIAS a [ j ] = temp ; } void b u b b l e S o r t ( int a [ ] , int n ) { int i , j ; f o r ( i = 0 ; i < n ; i ++) { f o r ( j = n ; j > i ; j −−) { i f ( a [ j −1] > a [ j ] ) i n t e r c a m b i a r ( a , j −1, j ) ; } } } Este codigo funciona perfectamente con arreglos de Numeros Enteros, pero que sucede si deseamos ordenar un arreglo de float o de double? O con alguna estructura que nosotros hemos definido? En C tendriamos que crear una funcion que tenga basicamente el mismo codigo, pero solo diferentes tipos, y ademas diferente nombre. En C++, podriamos traducir el codigo a una funcion plantilla: template <c l a s s T> void i n t e r c a m b i a r (T a [ ] , int i , int j ) { T temp = a [ i ] ; a[ i ] = a[ j ]; a [ j ] = temp ; } template <c l a s s T> void b u b b l e S o r t (T a [ ] , int n ) { int i , j ; f o r ( i = 0 ; i < n ; i ++) { f o r ( j = n ; j > i ; j −−) { i f ( a [ j −1] > a [ j ] ) i n t e r c a m b i a r ( a , j −1, j ) ; } } } El codigo es exactamente el mismo, solo que en lugar de aceptar un arreglo de Enteros, esta funcion acepta un arreglo de ”Ts”, que es nuestro Tipo Generico, y podemos realizar codigo como el siguiente: int a [ ] = { 2 , 9 , 6 , 8 , 1 , 3} float b [ ] = {2.0 , 4.0 , 9.0 , 1.0 , 6.0 , 0.0} bubbleSort (a , 6 ) ; bubbleSort (b , 6 ) ; /∗ En e s t e caso , T = i n t ∗/ /∗ En e s t e caso , T = f l o a t ∗/ Notese que el compilador realiza practicamente todo el trabajo, y selecciona y compila las funciones que requiere. Ademas no necesitamos especificar el tipo de dato que queremos usar con nuestra funcion, ya que este se detecta automaticamente. De hecho, se llaman igual que cualquier otra funcion. 1.8. Referencias Todos sabemos que en el caso de que necesitemos que una funcion modifique uno o varios de sus parametros, esto requiere que dichos parametros se pasen a la funcion como punteros. Mucha gente tiene problemas con punteros, 9 1.8. REFERENCIAS CAPÍTULO 1. INTRODUCCION A C++ se confunden simplemente. Gracias a dios, en C++ existe una forma mas ”elegante” de pasar parametros por referencia, y consiste en usar las llamadas ”Referencias”. La forma de declarar que una funcion toma una referencia a un cierto parametro, es usar un & (ampersand) de la misma forma que se declaran punteros a parametros, pero con la diferencia, que dentro del codigo de la funcion, el parametro se usa de la forma normal, osea podemos asignar a este, y la variable referenciada automaticamente cambia. Por ejemplo : void i n t e r c a m b i o ( int &a , int &b ) { int temp = a ; a = b; b = temp ; } int i = 2 , j = 3 ; intercambio ( i , j ) ; /∗ Ahora , i = 3 y j = 2 ∗/ Despues de ver este codigo, la ventaja de las Referencias sobre el uso de Punteros es bastante evidente. 10 Capı́tulo 2 Programacion Orientada a Objetos 2.1. Fundamentos 2.2. Implementacion en C++ 2.3. Plantillas de Clases 11