Cuando programas en C++, una de las tareas más comunes es almacenar colecciones de datos. Para ello, el lenguaje y su Biblioteca de Plantillas Estándar (STL) te ofrecen varias herramientas. Tres de las más fundamentales son los arreglos, los vectores (std::vector) y las listas (std::list).
Aunque a primera vista pueden parecer similares, su funcionamiento interno es muy diferente. Elegir la estructura correcta no es solo una cuestión de preferencia, sino una decisión crucial que puede afectar drásticamente el rendimiento y la eficiencia de tu programa.
Este artículo explica de manera concisa las diferencias clave para que sepas cuándo usar cada una.
1. Arreglos (C-style y std::array)
Los arreglos son la forma más básica y fundamental de almacenar una colección de elementos del mismo tipo.
Características Principales:
- Tamaño Fijo: Su tamaño se define en el momento de la compilación y no puede cambiar durante la ejecución del programa.
- Memoria Contigua: Todos sus elementos se almacenan uno al lado del otro en un bloque continuo de memoria.
- Acceso Rápido: Gracias a la memoria contigua, acceder a cualquier elemento por su índice es una operación extremadamente rápida (complejidad O(1)).
En C++, existen dos tipos de arreglos:
Arreglos estilo C
Son heredados del lenguaje C. Son simples y rápidos, pero también «inseguros», ya que no ofrecen ninguna protección contra el acceso fuera de sus límites.
// Arreglo de 5 enteros estilo C
int arreglo_c[5] = {10, 20, 30, 40, 50};
int primer_elemento = arreglo_c[0]; // Acceso muy rápido
std::array (C++11 y superior)
Es la versión moderna y segura de los arreglos. Es una envoltura (wrapper) que combina la velocidad de los arreglos de C con la seguridad y comodidad de los contenedores de la STL.
#include <array>
#include <iostream>
// Arreglo de 5 enteros usando std::array
std::array<int, 5> arreglo_moderno = {10, 20, 30, 40, 50};
// .size() nos da el tamaño
std::cout << "Tamaño: " << arreglo_moderno.size() << std::endl;
// .at(i) accede al elemento y verifica los límites
std::cout << "Elemento en el índice 2: " << arreglo_moderno.at(2) << std::endl;
Cuándo usar un arreglo: Úsalo cuando sepas exactamente cuántos elementos necesitas guardar y ese número no vaya a cambiar. Prefiere std::array sobre los arreglos estilo C en código C++ moderno.
2. std::vector
El std::vector es probablemente el contenedor más usado y versátil de la STL. Piensa en él como un arreglo dinámico y supervitaminado.
Características Principales:
- Tamaño Dinámico: Puede crecer y reducir su tamaño automáticamente según lo necesites.
- Memoria Contigua: Al igual que los arreglos, los vectores almacenan sus elementos en un bloque de memoria contiguo. Esto es su característica más importante.
- Acceso Rápido: Gracias a la memoria contigua, el acceso a cualquier elemento por su índice sigue siendo muy rápido (O(1)).
- Inserción al Final Eficiente: Añadir o quitar elementos al final del vector es muy rápido (una operación amortizada de O(1)).
- Inserción en Medio Lenta: Insertar o eliminar elementos al principio o en medio de un vector es una operación lenta (O(n)), porque obliga a desplazar todos los elementos que vienen después.
#include <vector>
#include <iostream>
// Vector de enteros, inicialmente vacío
std::vector<int> mi_vector;
// Añadimos elementos al final
mi_vector.push_back(10); // Vector: {10}
mi_vector.push_back(20); // Vector: {20}
mi_vector.push_back(30); // Vector: {30}
// Acceso rápido por índice
std::cout << "Elemento en el índice 1: " << mi_vector[1] << std::endl;
// Insertar en medio (operación lenta)
mi_vector.insert(mi_vector.begin() + 1, 15); // Vector: {10, 15, 20, 30}
Cuándo usar un vector: En la mayoría de los casos. std::vector debería ser tu contenedor de secuencia por defecto. Úsalo siempre que necesites una lista de tamaño variable y no tengas que hacer inserciones o eliminaciones frecuentes en medio de la lista.
3. std::list
El std::list es una lista doblemente enlazada. Su funcionamiento interno es radicalmente diferente al de los arreglos y vectores.
Características Principales:
- Memoria No Contigua: Cada elemento (llamado «nodo») se almacena en una ubicación de memoria separada. Cada nodo contiene el dato y dos punteros: uno al elemento anterior y otro al siguiente.
- Acceso Lento: No se puede acceder directamente a un elemento por su índice. Para llegar al quinto elemento, por ejemplo, tienes que recorrer los cuatro anteriores. Esto hace que el acceso sea lento (O(n)).
- Inserción en Cualquier Lugar Eficiente: Su gran ventaja. Insertar o eliminar elementos en cualquier parte de la lista (principio, medio o final) es una operación extremadamente rápida (O(1)), siempre que ya tengas un «iterador» que apunte a esa posición. Solo se necesita reajustar un par de punteros.
#include <list>
#include <iostream>
// Lista de enteros
std::list<int> mi_lista;
// Añadimos elementos
mi_lista.push_back(10); // Lista: {10}
mi_lista.push_back(30); // Lista: {10, 30}
mi_lista.push_front(5); // Lista: {5, 10, 30}
// Para insertar en medio, necesitamos un iterador
std::list<int>::iterator it = mi_lista.begin();
it++; // Apunta a 10
mi_lista.insert(it, 7); // Inserta 7 antes de 10. Lista: {5, 7, 10, 30}
// No se puede hacer mi_lista[2]
Cuándo usar una lista: Úsala solo cuando la principal operación que vayas a realizar sea insertar o eliminar elementos frecuentemente en medio de la colección y no necesites acceder a los elementos por su índice de forma rápida.
Tabla Comparativa
CaracterísticaArreglo (std::array)Vector (std::vector)Lista (std::list)
MemoriaContiguaContiguaNo Contigua (Nodos)
TamañoFijoDinámicoDinámico
Acceso AleatorioMuy Rápido (O(1))Muy Rápido (O(1))Lento (O(n))
Inserción/Borrado (Final)No aplicaRápido (Amortizado O(1))Rápido (O(1))
Inserción/Borrado (Medio)No aplicaLento (O(n))Rápido (O(1))
Conclusión: ¿Cuál Debería Usar?
La decisión puede resumirse en una regla simple para la mayoría de los casos:
- ¿Necesitas una colección de elementos? Empieza usando un
std::vector. Es la opción por defecto gracias a su buen rendimiento general y su flexibilidad. - ¿Sabes el tamaño exacto en tiempo de compilación y no cambiará? Usa un
std::array. Obtendrás la misma velocidad de un arreglo de C pero con más seguridad. - ¿Tu principal caso de uso es insertar y borrar elementos constantemente en el medio de la secuencia, y rara vez necesitas acceder a elementos por su posición? Solo en este escenario específico, considera usar un
std::list.
Para la mayoría de los programadores, std::vector será la herramienta adecuada para el 90% de las situaciones.
