Menu

Algoritmos de la STL de C++: guía práctica con ejemplos

Usa los algoritmos estándar de C++ - find, count_if, transform, accumulate, remove - para trabajar de verdad sobre rangos sin escribir bucles a mano, además de los detalles del par de iteradores y del idiom erase-remove.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Bucles que no tienes que escribir

En la página anterior viste que cada contenedor entrega iteradores: cursores ligeros con begin() y end(). Esa abstracción es la razón de ser de los algoritmos estándar. En lugar de escribir un bucle for a mano cada vez que quieres buscar, contar o transformar datos, llamas a una función con nombre de <algorithm> y le pasas un rango.

Un rango son simplemente dos iteradores: dónde empezar y uno más allá de dónde parar. Como todos los algoritmos hablan ese mismo lenguaje de iteradores, el mismo find funciona sobre un vector, un string o un array simple.

El patrón que debes interiorizar: un algoritmo de búsqueda devuelve el iterador end() para decir "no se encontró nada". Compara siempre con end() antes de desreferenciar el resultado: desreferenciar end() es comportamiento indefinido.

Contar y comprobar con predicados

Muchos algoritmos aceptan un predicado: una función (normalmente una lambda) que devuelve bool para cada elemento. count_if cuenta las coincidencias; all_of, any_of y none_of responden preguntas de sí/no sobre todo el rango.

std::count (sin _if) es el primo más sencillo que cuenta un valor exacto en lugar de una condición. Recurre a las versiones con predicado en cuanto tu prueba sea "cualquier cosa que cumpla una regla" en vez de "este valor concreto".

Transformar y reducir un rango

Dos caballos de batalla cubren la mayor parte del procesamiento de datos: std::transform mapea cada elemento a través de una función, y std::accumulate (de <numeric>, no de <algorithm>) pliega un rango hasta un único valor.

transform escribe sus resultados a través de un iterador de salida. Un error común y peligroso es apuntar la salida a un vector vacío: el algoritmo asume que ya hay espacio y escribe más allá del final. O bien dimensiona primero el destino, o usa un back_inserter para que cada resultado se añada con push_back.

accumulate parte de un valor inicial y combina los elementos de izquierda a derecha. El tipo del valor inicial importa: pasa 0 (un int) y la suma se calcula en int, lo que puede desbordarse o truncarse.

Si hubieras escrito accumulate(prices.begin(), prices.end(), 0) con una semilla int, cada suma ocurriría en int y los centavos desaparecerían. El tipo de la semilla determina silenciosamente el tipo del resultado.

El idiom erase-remove

Aquí está el detalle que sorprende a todo el mundo. std::remove no elimina nada del contenedor. Los algoritmos solo tienen iteradores, así que no pueden cambiar el tamaño de un contenedor: ni siquiera saben que existe uno. Lo que remove hace en realidad es mover todos los elementos conservados al principio, dejando la cola en un estado no especificado, y devolver un iterador al nuevo final lógico.

// remove por sí solo deja el tamaño sin cambios - este es el error:
remove(v.begin(), v.end(), 0);  // devuelve un iterador que ignoraste
// v todavía tiene su tamaño original; la cola es basura

Para borrar de verdad los elementos, emparejas remove con el erase del contenedor, y por eso se llama idiom erase-remove:

Usa remove_if para un predicado en lugar de un valor exacto. En C++20 puedes saltarte todo el baile con las funciones libres std::erase / std::erase_if, que hacen ambos pasos por ti: erase(v, 0);.

Los iteradores sobreviven a su validez bajo tu responsabilidad

Como los algoritmos devuelven iteradores, esos iteradores están sujetos a las mismas reglas de invalidación que viste en la página anterior. Guardar un iterador y luego modificar el contenedor - un push_back que provoque una reasignación, o un erase - puede dejar el iterador guardado colgando, y usarlo es comportamiento indefinido.

auto it = find(v.begin(), v.end(), 16);
v.push_back(99);   // puede reasignar el almacenamiento de v
cout << *it;       // BUG: `it` puede apuntar ahora a memoria liberada

El hábito seguro: usa el iterador que devuelve un algoritmo inmediatamente, antes de cualquier operación que pueda redimensionar o reasignar el contenedor. Si necesitas mutar el contenedor según un resultado, captura un índice (it - v.begin()) en su lugar, ya que los índices sobreviven a la reasignación.

Siguiente: Ordenación

Ya has visto cómo buscar, contar, transformar y reducir, pero un algoritmo es lo bastante importante como para merecer su propia página. std::sort reordena un rango en el sitio, y una vez que los datos están ordenados se abre toda una familia de algoritmos más rápidos y exclusivos para datos ordenados (binary_search, lower_bound, equal_range). A continuación profundizaremos en la ordenación: cómo proporcionar un comparador personalizado, la diferencia entre sort y stable_sort, y las reglas que tu función de comparación debe respetar para evitar comportamiento indefinido.

Preguntas frecuentes

¿Qué es la cabecera <algorithm> en C++?

<algorithm> es la cabecera de la biblioteca estándar que contiene funciones genéricas como std::find, std::sort, std::count_if y std::transform. Operan sobre rangos descritos por un par de iteradores (normalmente begin() y end()), por lo que el mismo algoritmo funciona con un vector, un array, un string o cualquier contenedor que exponga iteradores.

¿Cómo compruebo si un valor existe en un vector en C++?

Usa std::find: auto it = find(v.begin(), v.end(), target);. Si it == v.end() el valor no está presente; en caso contrario it apunta a la primera coincidencia. Para comprobar una condición en lugar de un valor exacto, usa std::any_of con un predicado.

¿Por qué std::remove no elimina realmente los elementos de mi contenedor?

Los algoritmos solo ven iteradores, no el contenedor, así que std::remove no puede reducirlo: desplaza los elementos que se conservan al principio y devuelve un iterador al nuevo final lógico. Debes seguirlo con v.erase(...) (el idiom erase-remove) para eliminar físicamente los sobrantes.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR