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