tema4 programación generica

51
Programación Avanzada - Tema 4: Programación Genérica http://aulavirtual.uji.es José Luis Llopis Borrás 22 de septiembre de 2010

Upload: pedro-hugo-valencia-morales

Post on 12-Jul-2015

1.339 views

Category:

Documents


4 download

TRANSCRIPT

Page 1: Tema4   programación generica

Programación Avanzada - Tema 4: Programación Genérica

http://aulavirtual.uji.es

José Luis Llopis Borrás

22 de septiembre de 2010

Page 2: Tema4   programación generica

Índice

1. Introducción 4

2. Funciones genéricas 5

2.1. Definición e instanciación . . . . . . . . . . . . . . . . . . . . . . . . . 7

2.2. Restricciones de uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

2.3. Especialización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

2.4. Sobrecarga . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

3. Clases genéricas 16

3.1. Definición de métodos . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

3.2. Organización del código fuente . . . . . . . . . . . . . . . . . . . . . . 21

3.3. Parámetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

3.4. Herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

4. Introducción a la STL 32

4.1. Contenedores secuenciales . . . . . . . . . . . . . . . . . . . . . . . . 33

Page 3: Tema4   programación generica

4.1.1. Instanciación . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

4.1.2. Operaciones comunes . . . . . . . . . . . . . . . . . . . . . . . 37

4.2. Algoritmos genéricos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

4.2.1. Aritmética de punteros . . . . . . . . . . . . . . . . . . . . . . . 40

4.2.2. Iteradores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

4.2.3. Algoritmos genéricos en la biblioteca STL . . . . . . . . . . . . . 47

4.2.4. Ejemplos de utilización de algoritmos genéricos . . . . . . . . . . 48

Page 4: Tema4   programación generica

1 Introducción

Programación Avanzada - Tema 4: Programación Genérica – 4

ä Ciertos algoritmos son independientes del tipo de datos sobre los que operan.

í Ejemplo: el cálculo del menor de dos valores enteros es idéntico al cálculo del

menor (alfabéticamente) de dos strings:

int min(int a, int b) { return a<b ? a : b; }

string min(string a, string b) { return a<b ? a : b; }

í Implementaremos estos algoritmos con funciones genéricas.

ä Ciertos tipos de datos pueden definirse de forma independiente (de al menos

alguno) de los tipos de datos que manipulan.

í Ejemplo: la implementación de una lista de enteros sólo difiere de la de una

lista de strings en el tipo de información que almacena.

í Implementaremos estos tipos de datos con clases genéricas.

ä La genericidad es un potente mecanismo que permite la reutilización de código.

Page 5: Tema4   programación generica

2 Funciones genéricas

Programación Avanzada - Tema 4: Programación Genérica – 5

ä Supongamos que debemos implementar una función que imprima en la salida

estándar los valores de un array de enteros:

void imp r im i r ( i n t v [ ] , i n t t a l l a ) {

cout < < "[ " ;

for ( i n t i = 0 ; i < t a l l a ; i ++)

cout < < v [ i ] < < " " ;

cout < < "]" < < endl ;

}

i n t main ( ) {

i n t v1 [ 5 ] = { 5 , 2 , 3 , 4 , 1 } ;

imp r im i r ( v1 , 5 )

}

[ 5 2 3 4 1 ]

Page 6: Tema4   programación generica

2 Funciones genéricas (II)

Programación Avanzada - Tema 4: Programación Genérica – 6

ä ...y más tarde necesitamos implementar otra función que imprima los valores de un

array de números reales:

void imp r im i r ( f l o a t v [ ] , i n t t a l l a ) {

cout < < "[ " ;

for ( i n t i = 0 ; i < t a l l a ; i ++)

cout < < v [ i ] < < " " ;

cout < < "]" < < endl ;

}

i n t main ( ) {

f l o a t v2 [ 3 ] = { 3 . 3 , 1 , 0 . 9 9 } ;

imp r im i r ( v2 , 3 )

}

[ 3 . 3 1 0 . 9 9 ]

ä Las dos versiones sólo se diferencian en el tipo base del array.

Page 7: Tema4   programación generica

2.1 Definición e instanciación

Programación Avanzada - Tema 4: Programación Genérica – 7

ä Implementamos una función genérica como única versión:

template < class T> void imp r im i r (T v [ ] , i n t t a l l a ) {

cout < < "[ " ;

for ( i n t i = 0 ; i < t a l l a ; i ++)

cout < < v [ i ] < < " " ;

cout < < "]" < < endl ;

}

i n t main ( ) {

i n t v1 [ 5 ] = { 5 , 2 , 3 , 4 , 1 } ;

f l o a t v2 [ 3 ] = { 3 . 3 , 1 , 0 . 9 9 } ;

imp r im i r ( v1 , 5 ) ;

imp r im i r ( v2 , 3 ) ;

}

[ 5 2 3 4 1 ]

[ 3 . 3 1 0 . 9 9 ]

Page 8: Tema4   programación generica

2.1 Definición e instanciación (II)

Programación Avanzada - Tema 4: Programación Genérica – 8

ä El compilador utiliza el código de la función genérica como plantilla para crear

automáticamente dos funciones sustituyendo T por un tipo concreto:

í Con T=int se crea la función void imprimir(int v[], int talla) para satisfacer la

llamada a imprimir(v1, 5).

í Con T=float se crea la función void imprimir(float v[], int talla) para satisfacer

la llamada a imprimir(v2, 3).

ä Esta operación recibe el nombre de instanciación.

Page 9: Tema4   programación generica

2.1 Definición e instanciación (III)

Programación Avanzada - Tema 4: Programación Genérica – 9

ä Vamos a añadir una función genérica

que permita la ordenación de arrays:

template < class T>

void i n te rcamb ia r (T & ip1 , T & ip2 ) {

T aux ;

aux = ip1 ;

ip1 = ip2 ;

ip2 = aux ;

}

/ / ordenación ascendente método burbuja

template < class T>

void ordenar (T v [ ] , i n t t a l l a ) {

for ( i n t i = t a l l a −1; i > = 1 ; i−−)

for ( i n t j = 0 ; j < i ; j ++)

i f ( v [ j ] > v [ j +1 ] )

i n te rcamb ia r ( v [ j ] , v [ j +1 ] ) ;

}

i n t main ( ) {

i n t v1 [ 5 ] = { 5 , 2 , 3 , 4 , 1 } ;

ordenar ( v1 , 5 ) ; / / T= i n t

imp r im i r ( v1 , 5 ) ; / / T= i n t

f l o a t v2 [ 3 ] = { 3 . 3 , 1 . 0 , 0 . 9 9 } ;

ordenar ( v2 , 3 ) ; / / T= f l o a t

imp r im i r ( v2 , 3 ) ; / / T= f l o a t

char v3 [ 5 ] = { ’e’ , ’o’ , ’a’ , ’u’ , ’i’ } ;

ordenar ( v3 , 5 ) ; / / T=char

imp r im i r ( v3 , 5 ) ; / / T=char

}

[ 1 2 3 4 5 ]

[ 0 . 9 9 1 3 . 3 ]

[ a e i o u ]

Page 10: Tema4   programación generica

2.1 Definición e instanciación (IV)

Programación Avanzada - Tema 4: Programación Genérica – 10

ä En la implementación de la función genérica imprimir() la única operación que

realizamos sobre valores de tipo T es la de salida de datos: cout << v[i]

í El compilador necesita de esa operación para crear instancias de la función.

í Esto impone una restricción sobre las posibles instancias: sólo se admitirán

aquellos tipos T para los que la operación de salida << esté definida.

ä ¿Qué restricciones impone las funciones genéricas ordenar() e intercambiar()?

í En la función ordenar() se necesita el operador > entre valores de tipo T.

í En la función intercambiar() se necesita que el operador = (asignación) esté

correctamente definido entre valores de tipo T.

í En la función intercambiar() se necesita que un constructor sin argumentos

que permita crear objetos de tipo T (constructor por omisión).

Page 11: Tema4   programación generica

2.2 Restricciones de uso

Programación Avanzada - Tema 4: Programación Genérica – 11

ä Teniendo en cuenta las restricciones mencionadas antes, ¿podemos instanciar

imprimir() y ordenar() para arrays de objetos de la clase Cadena (tal y como la

definimos en el Tema 2)?

Cadena v4 [ 3 ] = { "tigre" , "gato" , "leon" } ;

ordenar ( v4 , 3 ) ;

im p r im i r ( v4 , 3 ) ;

ä Para la función imprimir() no hay problema puesto que existe la función:

ostream & operator < < ( ostream & canal , const Cadena & c ) ;

ä Para la función intercambiar() tampoco, puesto que la asignación entre cadenas

está definida:

Cadena & Cadena : : operator = ( const Cadena & c ) ;

Page 12: Tema4   programación generica

2.2 Restricciones de uso (II)

Programación Avanzada - Tema 4: Programación Genérica – 12

ä Sin embargo, la función ordenar() necesita una función no definida en la clase

Cadena:

bool Cadena : : operator > ( const Cadena & c ) const ;

ä El compilador nos informa del error:

t e s t . cpp : In f u n c t i o n ‘ void ordenar (T∗ , i n t ) [ w i th T = Cadena ] ’:

test.cpp:247: instantiated from here

test.cpp:218: no match for ‘Cadena& > Cadena&’ operator

Page 13: Tema4   programación generica

2.3 Especialización

Programación Avanzada - Tema 4: Programación Genérica – 13

ä Para solucionar el problema, definimos una nueva versión no genérica de

ordenar() para arrays de objetos de tipo Cadena. Este proceso recibe el nombre

de especialización.

/ / e s pe c i a l i zac i ón para ar rays de cadenas

void ordenar ( Cadena v [ ] , i n t t a l l a ) {

for ( i n t i = t a l l a −1; i > = 1 ; i−−)

for ( i n t j = 0 ; j < i ; j ++)

i f ( strcmp ( v [ j ] . s t r , v [ j + 1 ] . s t r ) >0)

in te rcamb ia r ( v [ j ] , v [ j +1 ] ) ;

}

i n t main ( ) {

Cadena v4 [ 3 ] = { "tigre" , "gato" , "leon" } ;

ordenar ( v4 , 3 ) ;

imp r im i r ( v4 , 3 ) ;

}

[ gato leon t i g r e ]

Page 14: Tema4   programación generica

2.3 Especialización (II)

Programación Avanzada - Tema 4: Programación Genérica – 14

ä Observa que en la función ordenar() accedemos a la parte privada de la clase

Cadena en las expresiones v[j].str y v[j+1].str. Por tanto deberemos declarar la

función ordenar() como friend de Cadena.

ä En este ejemplo concreto, podemos optar por una segunda solución que no

necesita especializar la función genérica ordenar(): podemos simplemente dotar a

la clase Cadena del operador >.

class Cadena {

. . .

public :

. . .

bool operator >( const Cadena & s ) const ;

} ;

bool Cadena : : operator >( const Cadena & s ) const {

return ( strcmp ( s t r , s . s t r ) >0) ? true : fa lse ;

}

Page 15: Tema4   programación generica

2.4 Sobrecarga

Programación Avanzada - Tema 4: Programación Genérica – 15

ä Las funciones genéricas pueden ser

sobrecargadas de la misma forma

que las funciones ordinarias.

í Podemos especificar más de un

tipo genérico (segundo ejemplo

de la derecha).

í Al menos alguno de los

parámetros de la función debe ser

genérico, si no, el compilador no

puede saber cómo instanciar el

tipo T (tercer ejemplo de la

derecha).

template < class T>

T min (T a , T b ) {

return ( a<b ? a : b ) ;

}

template < class T1 , class T2>

T1 min ( T1 a , T2 b ) {

/ / . . .

}

template < class T> / / i n c o r r e c t o

T min ( i n t a , i n t b ) {

return ( a<b ? a : b ) ;

}

Page 16: Tema4   programación generica

3 Clases genéricas

Programación Avanzada - Tema 4: Programación Genérica – 16

Una clase genérica es una plantilla de definición de una clase que puede ser

instanciada en múltiples versiones concretas.

ä Declaramos la clase genérica Vector que almacena valores de tipo T:

template < class T>

class Vector {

T ∗ v ;

i n t t a l l a ;

public :

Vector ( i n t ) ;

Vector ( const Vector <T> &) ;

~Vector ( ) ;

Vector <T> & operator =( const Vector <T> &) ;

T & operator [ ] ( i n t ) ;

void imp r im i r ( ) const ;

i n t Ta l l a ( ) const { return t a l l a ; }

} ;

Page 17: Tema4   programación generica

3 Clases genéricas (II)

Programación Avanzada - Tema 4: Programación Genérica – 17

ä Podemos instanciar la clase Vector para T=int o T=Cadena:

i n t main ( ) {

Vector < int > v I n t ( 3 ) ; / / I n s t a n c i a c i ó n : T= i n t

for ( i n t i = 0 ; i < v I n t . Ta l l a ( ) ; i ++)

c in > > v I n t [ i ] ;

v I n t . im p r im i r ( ) ;

Vector <Cadena> vCad ( 3 ) ; / / I n s t a n c i a c i ó n : T=Cadena

for ( i n t i = 0 ; i <vCad . Ta l l a ( ) ; i ++)

c in > > vCad [ i ] ;

vCad . i mp r i m i r ( ) ;

}

3 4 5

[ 3 4 5 ]

hola que t a l

[ hola que t a l ]

Page 18: Tema4   programación generica

3.1 Definición de métodos

Programación Avanzada - Tema 4: Programación Genérica – 18

ä Las funciones miembro de una clase genérica son, a su vez, funciones

genéricas.

ä Atención: la clase Vector no existe como tal, sólo es una plantilla. Hay que

escribir, por tanto, Vector<T>.

template < class T> / / cons t r uc to r

Vector <T > : : Vector ( i n t t ) {

t a l l a = t ;

v=new T [ t a l l a ] ;

}

template < class T> / / cons t r uc to r copia

Vector <T > : : Vector ( const Vector <T> & unVector ) {

t a l l a = unVector . t a l l a ;

v = new T [ t a l l a ] ;

for ( i n t i = 0 ; i < t a l l a ; i ++)

v [ i ] = unVector . v [ i ] ;

}

Page 19: Tema4   programación generica

3.1 Definición de métodos (II)

Programación Avanzada - Tema 4: Programación Genérica – 19

template < class T> / / d e s t r u c t o r

Vector <T> : :~ Vector ( ) {

delete [ ] v ;

}

template < class T> / / operador as ignac ión

Vector <T> & Vector <T > : : operator =( const Vector <T> & unVector ) {

i f ( th is ! = & unVector ) {

i f ( t a l l a ! = unVector . t a l l a ) {

delete [ ] v ;

t a l l a = unVector . t a l l a ;

v = new T [ t a l l a ] ;

}

for ( i n t i = 0 ; i < t a l l a ; i ++)

v [ i ] = unVector . v [ i ] ;

}

return ∗ th is ;

}

Page 20: Tema4   programación generica

3.1 Definición de métodos (III)

Programación Avanzada - Tema 4: Programación Genérica – 20

template < class T> / / operador subínd ice

T & Vector <T > : : operator [ ] ( i n t i ) {

return v [ i ] ;

}

template < class T> / / func ión imp r im i r ( )

void Vector <T > : : im p r im i r ( ) const {

cout < < "[ " ;

for ( i n t i = 0 ; i < t a l l a ; i ++)

cout < < v [ i ] < < " " ;

cout < < "]" < < endl ;

}

Page 21: Tema4   programación generica

3.2 Organización del código fuente

Programación Avanzada - Tema 4: Programación Genérica – 21

El modelo tradicional de organización de

código fuente —modelo de compilación

separada—, no funciona con módulos

que contienen clases genéricas.

ä Recuerda que este modelo

consistente en separar la declaración

y la implementación de un módulo

(clase) en dos ficheros separados,

con extensiones .h y .cpp

respectivamente.

ä Veamos el ejemplo de la clase

MiClaseGenerica a continuación.

Listado 1: MiClaseGenerica.h

template <class T>

class MiClaseGenerica {

private :

T dato ;

public :

MiClaseGenerica (T ) ;

void imp r im i r ( ) const ;

} ;

Page 22: Tema4   programación generica

3.2 Organización del código fuente (II)

Programación Avanzada - Tema 4: Programación Genérica – 22

Listado 2: MiClaseGenerica.cpp

#include "MiClaseGenerica.h"

#include < iostream >

using namespace std ;

template < class T>

MiClaseGenerica <T > : : MiClaseGenerica (T x )

: dato ( x )

{ }

template < class T>

void MiClaseGenerica <T > : : imp r im i r ( ) const {

cout < < dato < < endl ;

}

Listado 3: TestMiClaseGenerica.cpp

#include "MiClaseGenerica.h"

i n t main ( ) {

MiClaseGenerica < int > a(123) ;

MiClaseGenerica < f loa t > b (3 .1416) ;

MiClaseGenerica <char > c (’#’ ) ;

a . imp r im i r ( ) ;

b . imp r im i r ( ) ;

c . imp r im i r ( ) ;

}

Page 23: Tema4   programación generica

3.2 Organización del código fuente (III)

Programación Avanzada - Tema 4: Programación Genérica – 23

ä Efectivamente, ¡la compilación falla!

Listado 4: Resultado de la compilación

$ g++ −c MiClaseGenerica . cpp

$ g++ −c TestMiClaseGenerica . cpp

$ g++ −o Test MiClaseGenerica . o TestMiClaseGenerica . o

/ usr / b in / l d : Undefined symbols :

MiClaseGenerica <char > : : MiClaseGenerica ( char )

MiClaseGenerica < f loa t > : : MiClaseGenerica ( f l o a t )

MiClaseGenerica < int > : : MiClaseGenerica ( i n t )

MiClaseGenerica <char > : : imp r im i r ( ) constMiClaseGenerica < f loa t > : : imp r im i r ( ) constMiClaseGenerica < int > : : imp r im i r ( ) constc o l l e c t 2 : l d re turned 1 e x i t s ta tus

Page 24: Tema4   programación generica

3.2 Organización del código fuente (IV)

Programación Avanzada - Tema 4: Programación Genérica – 24

ä ¿Por qué falla el modelo de compilación separada?

í Un clase genérica (template) no es una clase, sino un patrón que el compilador

usa para generar una o más clases.

í Para que el compilador pueda generar código, debe ver a la vez la

implementación de la clase (no sólo la declaración) y los tipos específicos

usados para instanciar el patrón.

ß Si deseamos usar MiClaseGenerica<int>, el compilador necesita ver a la

vez la implementación de la clase MiClaseGenerica<T> y el lugar donde

instanciamos T como int.

ß La implementación de MiClaseGenerica<T> está en un fichero y el lugar

donde instanciamos T en otro fichero. Ambos ficheros se compilan

separadamente y, por tanto, el compilador no sabe que tipo concreto es T

cuando compila la clase genérica, así que no genera ningún código.

Page 25: Tema4   programación generica

3.2 Organización del código fuente (V)

Programación Avanzada - Tema 4: Programación Genérica – 25

ä Existen 3 soluciones al problema:

1. Mover físicamente la implementación de la clase al fichero .h, con lo que

declaración e implementación estarán en el mismo lugar.

2. (Recomendada) Dejar por separado declaración e implementación, pero incluir

la implementación (fichero .cpp) donde se realiza la instanciación:

TestMiClaseGenerica.cpp#include "MiClaseGenerica.cpp"

. . .

3. Utilizar la palabra reservada export. Hacer un compilador que la soporte es

muy complicado. Sólo uno lo ha hecho: Comeau C++, pero no GCC.

MiClaseGenerica.hexport template <class T> class MiClaseGenerica {

. . .

Page 26: Tema4   programación generica

3.3 Parámetros

Programación Avanzada - Tema 4: Programación Genérica – 26

ä Las clases genéricas pueden tomar dos tipos de parámetros:

í Tipos genéricos, como los identificadores T, T1, T2, TipoBase, etc.

í Tipos ordinarios, como los tipos int o char.

ä En el siguient ejemplo definimos la clase VectorEstatico para almacener vectores

cuya talla es conocida en tiempo de compilación.

Clase VectorEstatico: declaración

template < i n t tam , class T> / / Parámetros : t i p o o r d i n a r i o i n t y t i p o genér ico T

class Vec to rEs ta t i co {

T v [ tam ] ;

public :

T & operator [ ] ( i n t ) ;

void imp r im i r ( ) const ;

} ;

Page 27: Tema4   programación generica

3.3 Parámetros (II)

Programación Avanzada - Tema 4: Programación Genérica – 27

ä ...y aquí está la implementación:

Clase VectorEstatico: implementación

template < i n t tam , class T>

void VectorEs ta t i co <tam , T > : : imp r im i r ( ) const {

cout < < "[ " ;

for ( i n t i = 0 ; i <tam ; i ++)

cout < < v [ i ] < < " " ;

cout < < "]" < < endl ;

}

template < i n t tam , class T>

T & VectorEs ta t i co <tam , T > : : operator [ ] ( i n t i ) {

return v [ i ] ;

}

Page 28: Tema4   programación generica

3.3 Parámetros (III)

Programación Avanzada - Tema 4: Programación Genérica – 28

ä La instanciación de clases con parámetros constantes se realiza así:

i n t main ( ) {

Vec to rEs ta t i co <3 , int > v3 In t ;

for ( i n t i = 0 ; i < 3 ; i ++)

c in > > v3 In t [ i ] ;

v 3 I n t . im p r im i r ( ) ;

Vec to rEs ta t i co <5 , int > v5 In t ;

for ( i n t i = 0 ; i < 5 ; i ++)

c in > > v5 In t [ i ] ;

v 5 I n t . im p r im i r ( ) ;

}

ä Atención: los tipos de las variables v3Int y v5Int son diferentes e incompatibles:

í v3Int es del tipo VectorEstatico de 3 componentes enteras.

í v5Int es del tipo VectorEstatico de 5 componentes enteras.

Page 29: Tema4   programación generica

3.4 Herencia

Programación Avanzada - Tema 4: Programación Genérica – 29

Podemos derivar clases genéricas de otras clases genéricas.

ä Ejemplo: definimos la clase VectorSeguro como especialización de Vector. Ahora

el operator[] comprobará si el índice está fuera de rango.

Clase VectorSeguro: declaración

template < class T>

class VectorSeguro : public Vector <T > {

public :

VectorSeguro ( i n t i ) : Vector <T>( i ) { }

T & operator [ ] ( i n t ) ;

} ;

ä Observa que usamos la lista de inicialización para pasar un argumento al

constructor de la clase base.

Page 30: Tema4   programación generica

3.4 Herencia (II)

Programación Avanzada - Tema 4: Programación Genérica – 30

ä Observa que en la sentencia return llamamos a la función operator[ ] de la clase

base.

Clase VectorSeguro: implementación

template < class T>

T & VectorSeguro <T > : : operator [ ] ( i n t i ) {

i f ( i < 0 | | i > this−>Ta l l a ( ) ) {

ce r r < < "Error: Indice fuera de rango" < < endl ;

e x i t ( 1 ) ;

}

return Vector <T > : : operator [ ] ( i ) ;

}

Page 31: Tema4   programación generica

3.4 Herencia (III)

Programación Avanzada - Tema 4: Programación Genérica – 31

ä Probamos la clase VectorSeguro:

i n t main ( ) {

VectorSeguro < int > v ( 3 ) ;

for ( i n t i = 0 ; i <v . Ta l l a ( ) ; i ++)

c in > > v [ i ] ;

v . i mp r im i r ( ) ;

v [10 ]=33 ;

}

7 8 9

[ 7 8 9 ]

Er ro r : I nd i ce fuera de rango

Page 32: Tema4   programación generica

4 Introducción a la STL

Programación Avanzada - Tema 4: Programación Genérica – 32

La biblioteca estándar de plantillas (STL) contiene componentes genéricos de

(básicamente) dos tipos:

ä Contenedores

í Son clases genéricas que sirven para almacenar (contener) colecciones de

elementos de algún tipo. Ejemplos: vector, list, set, map, etc.

ä Algoritmos genéricos

í Son operaciones aplicables a los contenedores y los arrays.

í Son independientes del tipo de datos concreto sobre el que operan (int, char,

string, . . . ). Por ello están implementadas usando funciones genéricas.

í Son independientes del tipo de contenedor en que los elementos están

almacenados (array, list, vector, set, . . . ). Esta independencia se consigue

gracias a que no operan directamente sobre ellos, sino a través de iteradores.

Page 33: Tema4   programación generica

4.1 Contenedores secuenciales

Programación Avanzada - Tema 4: Programación Genérica – 33

Un contenedor secuencial contiene una colección ordenada de elementos de un

mismo tipo. Veamos dos de los más importantes:

ä vector

í Mantiene sus elementos en un área contigua de memoria.

í El acceso aleatorio es eficiente: es igual de rápido acceder al elemento 5o que

al 17o o al 9o.

í El operador de subíndice [ ] permite acceder a sus componentes.

í Sin embargo, la inserción de un elemento en cualquier posición que no sea la

última es ineficiente ya que implica el desplazamiento de los elementos a la

derecha del insertado.

í Por el mismo motivo, el borrado de un elemento distinto del último es también

ineficiente.

Page 34: Tema4   programación generica

4.1 Contenedores secuenciales (II)

Programación Avanzada - Tema 4: Programación Genérica – 34

ä list

í Mantiene sus elementos en áreas de memoria no contigua (concretamente,

nodos doblemente enlazados).

í La inserción y borrado de elementos en cualquier posición es eficiente.

í Sin embargo, el acceso aleatorio es menos eficiente: para acceder al elemento

5o, 17o y 9o hay que visitar todos los intermedios.

Page 35: Tema4   programación generica

4.1.1 Instanciación

Programación Avanzada - Tema 4: Programación Genérica – 35

ä Para usar un contenedor secuencial, debemos incluir el fichero de cabecera

asociado:

#include <vector>

#include <list>

ä Disponemos de cinco modos de instanciar un objeto contenedor secuencial:

1. Crear un contenedor vacío:

vector<int> v1;

list<Complejo> lista1;

2. Crear un contenedor de algún tamaño no necesariamente constante. Cada

elemento se inicializa con el valor por defecto (los int o double con cero):

vector<int> v2 (100);

list<Cadena> lista2 (talla);

Page 36: Tema4   programación generica

4.1.1 Instanciación

Programación Avanzada - Tema 4: Programación Genérica – 36

3. Crear un contenedor de un tamaño y valor inicial dado:

vector<string> v3 (16, "una cadena" );

list<float> lista3 (100, 3.1416 );

4. Crear un contenedor como copia de otro:

vector<string> v4 (v3);

list<float> lista4 (lista3);

5. Crear un contenedor usando un par de iteradores. (Más adelante volveremos a

tratar este asunto).

Page 37: Tema4   programación generica

4.1.2 Operaciones comunes

Programación Avanzada - Tema 4: Programación Genérica – 37

ä Algunas operaciones comunes:

í ¿Qué talla tiene el contenedor? ¿Está vacío?: size() y empty()

í La asignación, el test de igualdad y desigualdad: =, == y !=

í Acceso al primer y último elemento: front() y back()

í Inserción y borrado del último: push_back() y pop_back()

ä El contenedor list, pero no el vector, permite además:

í Inserción y borrado del primero: push_front() y pop_front()

Page 38: Tema4   programación generica

4.1.2 Operaciones comunes (II)

Programación Avanzada - Tema 4: Programación Genérica – 38

Ejemplo de uso

#include < iostream >

#include < l i s t >

using namespace std ;

i n t main ( ) {

l i s t < int > miL is ta ; i n t va lo r ;

while ( c in > > v a lo r )

m iL i s ta . push_back ( va l o r ) ;

cout < < "miLista = { " ;

while ( ! m iL i s ta . empty ( ) ) {

cout < < miL is ta . f r o n t ( ) < < ’ ’ ;

m iL i s ta . pop_f ront ( ) ;

}

cout < < ’}’ < < endl ;

}

Salida

12

45

33

111

35

miL is ta = { 1 2 4 5 3 3 1 1 1 3 5 }

Page 39: Tema4   programación generica

4.2 Algoritmos genéricos

Programación Avanzada - Tema 4: Programación Genérica – 39

Supongamos que necesitamos escribir una función que realice la búsqueda secuencial

de la primera aparición de un valor determinado en un contenedor vector y devuelva

un puntero a él o cero (NULL) si no se encuentra:

template < class T> const T ∗ f i n d ( const vector <T> & v , const T & va lo r ) {

for ( i n t i = 0 ; i <v . s ize ( ) ; i ++)

i f ( v [ i ]== v a lo r )

return &v [ i ] ;

return NULL;

}

ä ¿Es posible modificar la función para que también funcione con arrays? ¿Y con

otros contenedores secuenciales?

ä Si conseguimos estos dos objetivos habremos implementado un algoritmo

genérico de búsqueda secuencial aplicable a cualquier contenedor secuencial

incluyendo los arrays. El problema radica en encontrar un tratamiento común

para arrays y contenedores.

Page 40: Tema4   programación generica

4.2.1 Aritmética de punteros

Programación Avanzada - Tema 4: Programación Genérica – 40

ä Planteamos una versión de find() para arrays:

template < class T>

T ∗ f i n d (T ∗v , i n t tam , const T & va lo r ) {

i f ( ! v | | tam<1) return 0 ;

for ( i n t i = 0 ; i <tam ; ++ i , ++ v )

i f ( ∗ v == v a lo r ) return v ;

return 0 ; / / NULL

}

ä Esta versión realiza bien su cometido pero hemos

tenido que introducir un nuevo argumento, tam (la talla

del array ), puesto que esa información es necesaria.

ä Debemos evitar dicho parámetro ya que los

contenedores como vector o list ya contienen

internamente su talla.

v

0 1 2 tam-1

Page 41: Tema4   programación generica

4.2.1 Aritmética de punteros (II)

Programación Avanzada - Tema 4: Programación Genérica – 41

ä Planteamos una segunda versión de find() para arrays

en la que sustituimos el tamaño del array por un

puntero que sirve como centinela que marca el final:

template < class T>

T ∗ f i n d (T ∗ f i r s t , T ∗ l a s t , const T & va lo r ) {

i f ( ! f i r s t | | ! l a s t ) return 0 ;

while ( f i r s t ! = l a s t )

i f ( ∗ f i r s t == v a lo r ) return f i r s t ;

else ++ f i r s t ;

return 0 ; / / NULL

}

i n t main ( ) {

i n t ar ray [ 5 ] = { 1 , 3 , 5 , 7 , 1 1 } ;

i f ( f i n d ( array , ar ray +5 , 7 ) )

\ \ . . .

1 3 5 7 11

array

array+5

first last

last y array+5 apuntan al elemento siguiente del

último, aunque no exista

Page 42: Tema4   programación generica

4.2.2 Iteradores

Programación Avanzada - Tema 4: Programación Genérica – 42

ä Gracias a la aritmética de punteros hemos encontrado una buena estrategia para

pasar los argumentos a find(): utilizamos la dirección del primer elemento del array

y la del elemento siguiente al último.

ä Para aplicar la misma estrategia a contenedores secuenciales, deberemos usar

una abstracción del concepto de puntero: el iterador:

í Cada contenedor define el tipo: contenedor<tipobase>::iterator

í Cada contenedor define las funciones begin() y end() que devuelven iteradores

que “apuntan” al primero y al siguiente del último elementos respectivamente.

Page 43: Tema4   programación generica

4.2.2 Iteradores (II)

Programación Avanzada - Tema 4: Programación Genérica – 43

i n t main ( ) {

vector < int > v i ;

/ / . . . re l lenamos v i

i f ( f i n d ( v i . begin ( ) , v i . end ( ) , 7 ) )

/ / . . .

l i s t < int > l i ;

/ / . . . re l lenamos l i

i f ( f i n d ( l i . begin ( ) , l i . end ( ) , 7 ) )

/ / . . .

}

ä Aquí usamos una nueva versión de

find() que utiliza iteradores en lugar

de punteros. Implementaremos esta

versión más adelante.

...

vi.begin()vector<int>::iterator

vivector<int>

vi.end()vector<int>::iterator

lilist<int>

li.begin()list<int>::iterator

li.end()list<int>::iterator

Page 44: Tema4   programación generica

4.2.2 Iteradores (III)

Programación Avanzada - Tema 4: Programación Genérica – 44

ä Los iteradores son objetos que

soportan el mismo conjunto de

operaciones que los punteros: ++,

*, == y != pero tienen una

implementación diferente para cada

contenedor.

ä En el ejemplo de la derecha,

recorremos una lista usando un

iterador.

ä Observa que creamos el contendor li

usando un par de iteradores (en este

caso, punteros) sobre un array.

#include < iostream >

#include < l i s t >

usign namespace s td ;

i n t main ( ) {

i n t primos [ 5 ] = { 1 , 3 , 5 , 7 , 1 1 } ;

l i s t < int > l i ( primos , primos +5) ;

l i s t < int > : : i t e r a t o r i t e r = l i . begin ( ) ;

cout < < "{ " ;

while ( i t e r != l i . end ( ) ) {

cout < < ∗ i t e r < < " " ;

++ i t e r ;

}

cout < < "}" < < endl ;

}

{ 1 3 5 7 1 1 }

Page 45: Tema4   programación generica

4.2.2 Iteradores (IV)

Programación Avanzada - Tema 4: Programación Genérica – 45

ä Volviendo a nuestra función find(), aquí tenemos la versión definitiva, válida para

todo tipo de contenedor secuencial y cualquier array : hemos conseguido un

algoritmo genérico usando iteradores como abstracción del concepto de puntero:

template < class I t e rado r , class T>

I t e r a d o r f i n d ( I t e r a d o r f i r s t , I t e r a d o r l a s t , const T & va lo r ) {

while ( f i r s t ! = l a s t ) {

i f ( ∗ f i r s t == v a lo r )

return f i r s t ;

f i r s t ++;

}

return l a s t ;

}

ä Atención: Esta función find() es uno de los algoritmos genéricos de la biblioteca

estándar.

Page 46: Tema4   programación generica

4.2.2 Iteradores (V)

Programación Avanzada - Tema 4: Programación Genérica – 46

i n t main ( ) {

i n t primos [ 5 ] = { 1 , 3 , 5 , 7 , 1 1 } ;

i n t ∗ p t r ;

p t r = f i n d ( primos , primos +5 , 7 ) ;

i f ( p t r ! = primos +5)

cout < < "Encontrado 7" < < endl ;

elsecout < < "No encontrado 7" < < endl ;

l i s t < s t r i n g > l s ;

l s . push_f ront ("hola" ) ;

l s . push_f ront ("hello" ) ;

l i s t < s t r i n g > : : i t e r a t o r i t ;

i t = f i n d ( l s . begin ( ) , l s . end ( ) ,"bonjour" ) ;

i f ( i t ! = l s . end ( ) )

cout <<"Encontrado bonjour"<<endl ;

elsecout <<"No encontrado bonjour"<<endl ;

}

$ g++ −o prueba prueba . cpp

$ prueba

Encontrado 7

No encontrado bonjour

Page 47: Tema4   programación generica

4.2.3 Algoritmos genéricos en la biblioteca STL

Programación Avanzada - Tema 4: Programación Genérica – 47

En la STL hay definidos más de 60 algoritmos genéricos:

ä Algoritmos de búsqueda: find(), binary_search(), count(), search(), find_if(),

count_if(), . . .

ä Algoritmos de ordenación: sort(), partial_sort(), merge(), reverse(), rotate(), . . .

ä Algoritmos de copiado, borrado y sustitución: copy(), remove(), remove_if(),

replace(), swap(), . . .

ä Algoritmos relacionales: equal(), includes(), mismatch(), . . .

ä Algoritmos de generación y transformación: fill(), for_each(), generate(),

transform(), . . .

ä Algoritmos numéricos: accumulate(), partial_sum(), inner_product(), . . .

ä Algoritmos sobre conjuntos: set_union(), set_difference(), . . .

Page 48: Tema4   programación generica

4.2.4 Ejemplos de utilización de algoritmos genéricos

Programación Avanzada - Tema 4: Programación Genérica – 48

ä Para utilizar los algoritmos genéricos hay que incluir el fichero de cabecera

correspondiente: #include <algorithm>

ä Supongamos, por ejemplo, que necesitamos implementar la función is_elem() de

modo que devuelva true si un número dado es un elemento de un vector ordenado

de enteros. Podemos usar cuatro posibles algoritmos genéricos:

1. find() busca un valor en una colección no ordenada marcada con dos

iteradores first y last. Si se encuentra el valor, devuelve un iterador al valor. En

caso contrario, devuelve last.

2. binary_search() busca en una colección ordenada. Devuelve true o false si

encuentra el valor o no. Es más eficiente que find().

3. count() devuelve el número de repeticiones de un valor.

4. search() busca una subsecuencia en una secuencia. Devuelve un iterador al

principio de la subsecuencia.

Page 49: Tema4   programación generica

4.2.4 Ejemplos de utilización de algoritmos genéricos (II)

Programación Avanzada - Tema 4: Programación Genérica – 49

ä Una posible implementación de la función is_elem() puede usar binary_search(),

ya que tenemos la garantía de que la secuencia de números está ordenada.

#include < algor i thm >#include < vector >#include < iostream >

using namespace s td ;

bool is_elem ( const vector < int > &v , i n t va lo r ) {return binary_search ( v . begin ( ) , v . end ( ) , va l o r ) ;

}

i n t main ( ) {i n t ar ray [8 ]= {1 ,2 ,3 ,5 ,7 ,11 ,13 ,17 } ;

vector < int > primos ( array , ar ray +8) ;

i f ( is_elem ( primos , 1 1 ) )cout < < "11 esta en primos" ;

elsecout < < "11 no esta en primos" ;

}

Page 50: Tema4   programación generica

4.2.4 Ejemplos de utilización de algoritmos genéricos (III)

Programación Avanzada - Tema 4: Programación Genérica – 50

ä La función is_elem(), como hemos visto, supone que el vector de enteros que

recibe está ordenado. Pero, ¿y si no está garantizado?

ä Podemos escribir una nueva versión que hace una copia del contenedor, la ordena

y sobre la versión ordenada realiza la búsqueda:

#include < algor i thm >

#include < vector >

#include < iostream >

using namespace s td ;

bool is_elem ( const vector < int > &v , i n t va lo r ) {

vector < int > copia ( v . s i ze ( ) ) ;

copy ( v . begin ( ) , v . end ( ) , copia . begin ( ) ) ;

s o r t ( copia . begin ( ) , copia . end ( ) ) ;

return binary_search ( copia . begin ( ) , copia . end ( ) , va l o r ) ;

}

Page 51: Tema4   programación generica

Fin

Copyright c© 2010 José Luis Llopis Borrás

Realizada con ujislides c© 2002-3 Sergio Barrachina ([email protected])