Escribiendo funciones genéricas en C++

Anuncio
Escribiendo funciones genéricas
Escribiendo funciones
genéricas en C++
• Una función genérica se puede aplicar a
objetos de diferentes tipos que comparten
un comportamiento común
• C++ utiliza plantillas (templates) para
poder escribir funciones aplicables a
diferentes objetos
Programación Orientada a Objeto
Ing. Civil en Telecomunicaciones
Plantillas pueden ser parametrizadas
para
considerar diferencias entre objetos
Mediana de un vector de double
double mediana(vector<double> v) {
typedef vector<double>::size_type vec_size;
vec_size size = v.size();
if (size == 0)
throw domain_error(“Mediana de vector vacio”);
Mediana de un vector genérico
template <class T> T mediana(vector<T> v)
{
typedef typename vector<T>::size_type vec_size;
vec_size size = v.size();
if (size == 0)
throw domain_error(“Mediana de vector vacio”);
sort(v.begin(), v.end());
sort(v.begin(), v.end());
vec_size mid = size/2;
vec_size mid = size/2;
return (size%2 == 0) ? (v[mid] + v[mid – 1])/2
: v[mid];
}
return (size%2 == 0) ? (v[mid] + v[mid – 1])/2
: v[mid];
}
Tipo de la función
Comando typename
• Función genérica define una plantilla
donde el tipo de dato retornado es T
• El comando typename informa que
vector<T>::size_type es un tipo de
dato
template <typename T> ó
template <class T>
• T es definida al momento de compilación
(binding)
Sea v_int un vector<int>
vector<double>::size_type y
vector<int>::size_type son tipos de
datos estándares, pero la versión genérica
vector<T>::size_type no lo es.
Al escribir mediana(v_int), se instancia
una versión del código genérico donde T es
int
© 2015 Mario Medina C.
1
Instanciación de plantillas
• El compilador creará diferentes versiones
de la función dependiendo del tipo de los
argumentos
cada uso explícito de T por el
nuevo tipo de dato
Reemplazando
• Muchas veces la instanciación es hecha por
el programa enlazador (linker)
Funciones genéricas y tipos
• Sea la siguiente función genérica que
obtiene el máximo de 2 argumentos dados
como referencias
template <typename T> T max(const T& izq,
const T& der) {
return izq > der ? izq : der;
}
Errores de programación aparecen
al
momento de enlazar con las bibliotecas
Funciones genéricas y tipos
Tipos de datos implícitos y explícitos
• Sean i, j enteros y d, e variables double
• El código usa el tipo T en forma explícita
Invocación max(i, j) instancia versión de
En el argumento
max() para int
Invocación max(d, e) instancia versión de
max() para double
En la definición
• Qué hace max(i, e)?
Error! Compilador no saber
si convertir i a
double o convertir e a int
Usuario debe convertir los datos para que sean
del mismo tipo
Tipos de iteradores
• No todos los contenedores soportan las
mismas operaciones
Por ejemplo, vector permite acceso aleatorio
• Biblioteca estándar define 5 tipos de
iteradores básicos
Pueden ser usados en sus funciones genéricas
• Algoritmos estándares definen qué tipo de
iterador esperan como argumento
© 2015 Mario Medina C.
de vec_size
En el tipo de dato retornado
• El código usa el tipo T en forma implícita
En las operaciones v[mid
– 1])/2
Tipo de dato y contenedor deben aceptar
índices, operaciones de suma y división
Función genérica no sirve para string!
Iterador de entrada
• Conocido también como input iterator
• Permite acceso secuencial de sólo lectura
(read-only) a elementos de un contenedor
• Requiere
Operaciones ++ (pre-incremento
y postincremento)
Comparación == y !=
Dereferencia (*it)
Equivalencia entre it->m y (*it).m
2
Búsqueda genérica secuencial
Otra búsqueda genérica secuencial
• Función genérica busca elemento x en un
contenedor, entre iteradores de entrada
In begin y end
• Función genérica recursiva busca
elemento x en el contenedor entre
iteradores In begin y end
template <class In, class X> In find(In
begin, In end, const X& x) {
while (begin != end && *begin != x)
++begin;
return begin;
}
template <class In, class X> In find(In
begin, In end, const X& x) {
if (begin == end && *begin == x)
return begin;
begin++;
return find(begin, end, x);
}
Iterador de salida
Copia genérica secuencial
• Conocido también como output iterator
• Permite acceso secuencial de sólo
escritura (write-only) a elementos de un
contenedor
• Requiere
• Función genérica copia elementos desde
un contenedor entre iteradores de
entrada In begin y end, al iterador de
salida Out
Operaciones ++ (pre-incremento
y post-
incremento)
Asignación (*it = valor)
template <class In, class Out> Out
copy(In begin, In end, Out dest) {
while (begin != end)
*dest++ = *begin++;
return dest;
}
Iterador para escritura secuencial
Iterador de entrada/salida
• Un iterador de salida se usa para escritura
secuencial
• Conocido también como forward iterator
• Permite acceso secuencial de lectura y
escritura a elementos de un contenedor
• Requiere operaciones de input iterator y
del output iterator
Programa que usa iterador de salida no
puede realizar accesos no secuenciales
Tampoco realizar múltiples asignaciones
No pueden hacerse las siguientes operaciones
*it = x;
++it;
++it;
*it = y;
© 2015 Mario Medina C.
*it = x;
*it = y;
No restringe múltiples asignaciones
3
Reemplazo secuencial
Iterador bidireccional
• Función genérica reemplaza elementos iguales a
x por y en un contenedor entre iteradores de
entrada/salida For begin y end
• Permite acceso secuencial de lectura y
escritura a elementos de un contenedor
en dirección creciente y decreciente
• Requiere operaciones de forward iterator
template
begin,
while
if
<class For, class X> void replace(For
For end, const X& x, const X& y) {
(begin != end) {
(*begin == x)
*begin = y;
++begin;
Requiere además operación -- (post-
decremento y pre-decremento)
}
}
Inversión de secuencia
Iterador de acceso aleatorio
• Función genérica invierte el orden de un
contenedor entre iteradores
bidireccionales Bi begin y end
• Conocido también como random-access
iterator
• Permite acceso aleatorio de lectura y
escritura a elementos de un contenedor
template <class Bi> void reverse(Bi begin,
Bi end) {
while (begin != end) {
--end;
if (begin != end)
swap(*begin++, *end);
}
Operaciones para random-access
iterator
• Todas las operaciones definidas para
bidirectional iterator
• Sean p y q iteradores, y n un entero
p
+ n, p – n, n + p
Retorna un iterador
p
– q
Retorna un entero
p[n]
equivalente a *(p + n)
p < q, p > q, p <= q, p >= q
© 2015 Mario Medina C.
Búsqueda binaria
• Función genérica busca en forma binaria el elemento x
entre iteradores aleatorios Ran begin y end
template <class Ran, class X> bool binarySearch(Ran begin,
Ran end, const X& x) {
while (begin < end) {
Ran mid = begin + (end – begin)/2;
if (x < *mid)
end = mid;
else if (*mid < x)
begin = mid + 1;
else
return true;
}
return false;
}
4
Usando iteradores
Función divide para string
• Iteradores de entrada y salida pueden ser
usados para recibir datos y para generar
salidas
vector<string> divide(const string& s)
{
typedef string::const_iterator iter;
vector<string> ret;
iter i = s.begin();
while (i != s.end()) {
i = find_if(i, s.end(), no_espacio);
iter j = find_if(i, s.end(), espacio);
if (i != s.end())
ret.push_back(string(i, j));
i = j;
}
return ret;
Ejemplo: función divide que genera
vector<string> con las palabras que hay
en un string
Modificar función para que escriba las
palabras encontradas a un iterador de salida
}
Función divide usando iterador de
salida
Ejemplo: mediana de una lista
template <class Out> divide(const string& s, Out
os) {
typedef string::const_iterator iter;
• Objetos list de la biblioteca estándar no
permiten acceso aleatorio a datos
iter i = s.begin();
while (i != s.end()) {
i = find_if(i, s.end(), no_espacio);
iter j = find_if(i, s.end(), espacio);
if (i != s.end())
*os++ = string(i, j);
i = j;
}
}
No permite iteradores aleatorios
• Cálculo de mediana requiere recorrer la
lista usando forward iterador
Si número de elementos es impar, retorna
*iter
Si número de elementos es par, retorna
(*iter + *(iter + 1))/2
Ejemplo: mediana de una lista (I)
Ejemplo: mediana de una lista (II)
double medianaDouble(list<double> l) {
typedef list<double>::size_type list_size;
list_size size = l.size();
if (size%2 == 1) {
while (posicion++ != mid) {
iter++;
}
return *iter;
}
else {
while (posicion++ != (mid - 1)) {
iter++;
}
}
temp = *iter++;
temp = (temp + *iter)/2.0;
return temp;
}
if (size == 0)
throw domain_error("Mediana de una lista
vacio");
l.sort();
list_size mid = size/2;
list<double>::const_iterator iter = l.begin();
unsigned int posicion = 0;
double temp;
© 2015 Mario Medina C.
5
Mediana de una lista genérica
• Ejemplo anterior es aplicable a una lista de
double
• Cómo se modifica para ser aplicable para
una lista genérica?
Cambiar double por tipo genérico T
Definir typedefs como typename
• Código mostrado supone que los elementos
de la lista pueden sumarse y dividirse por 2
Mediana de lista genérica
if (size%2 == 1) {
while (posicion++ != mid) {
iter++;
}
return *iter;
}
else {
while (posicion++ != (mid - 1)) {
iter++;
}
}
temp = *iter++;
temp = (temp + *iter)/2.0;
return temp;
}
Ejemplo: función find_all()
• Generalizaremos la función anterior para
que busque todas las ocurrencias de un
valor v en un contenedor c
• Es necesario parametrizar el tipo del
contenedor, C , y el tipo del valor , V
© 2015 Mario Medina C.
Mediana de lista genérica
template <class T> T mediana(list<T> l) {
typedef typename list<T>::size_type list_size;
list_size size = l.size();
if (size == 0)
throw domain_error("Mediana de una lista
vacio");
l.sort();
list_size mid = size/2;
typename list<T>::const_iterator iter =
l.begin();
unsigned int posicion = 0;
T temp;
Ejemplo: función find_all()
// Funcion retorna un vector conteniendo
// iteradores a todas las ocurrencias del
// carácter c en el string s
vector<string::iterator> find_all(string& s,
char c) {
vector<string::iterator> res;
for (auto p = s.begin(); p != s.end(); ++p) {
if (*p == c) {
res.push_back(p);
}
}
return res;
}
Ejemplo: función genérica
find_all()
// Funcion retorna un vector conteniendo
// iteradores a todas las ocurrencias del
// valor v en el contenedor C
template <typename C, typename V> vector<typename
C::iterator> find_all(C& c, V v) {
vector<typename C::iterator> res;
for (auto p = s.begin(); p != s.end(); ++p) {
if (*p == v) {
res.push_back(p);
}
}
return res;
}
6
Ejemplo: función find_all()
• El comando typename es usado para
informar al compilador que el iterador al
contenedor C es un tipo y no un dato
• Alternativamente, puede usarse un alias
para el iterador a través de los comandos
typename y using
© 2015 Mario Medina C.
Ejemplo: función genérica
find_all()
// Funcion retorna un vector conteniendo
// iteradores a todas las ocurrencias del
// valor v en el contenedor C
template <typename T>
using iterator = typename T::iterator;
template <typename C, typename V>
vector<iterator<C>> find_all(C& c, V v) {
vector<iterator<C>> res;
for (auto p = s.begin(); p != s.end(); ++p) {
if (*p == v) {
res.push_back(p);
}
}
return res;
}
7
Descargar