Por qué usar vector en lugar de un array nativo
Un array nativo tiene un tamaño fijo fijado en tiempo de compilación y olvida cuántos elementos tiene en cuanto lo pasas a una función. std::vector resuelve ambos problemas: es un array redimensionable que controla su propia longitud, crece bajo demanda y limpia su propia memoria. En C++ moderno, vector es el contenedor por defecto; recurre a un array nativo solo cuando tengas una razón concreta para no hacerlo.
Incluye <vector> y luego declara uno con el tipo del elemento entre corchetes angulares:
scores.size() siempre te dice la longitud actual: no hace falta un int n aparte que mantener sincronizado, ni trucos con sizeof. El {90, 75, 100, 60} es un inicializador entre llaves; el vector deduce que necesita cuatro posiciones.
Crear e inicializar un vector
Hay varias formas de construir uno, según lo que sepas de antemano:
Cuidado con la trampa de paréntesis frente a llaves: vector<int> tens(5, 10) crea cinco copias de 10, mientras que vector<int> tens{5, 10} crea un vector de dos elementos que contiene 5 y 10. Los paréntesis significan "tamaño y valor de relleno"; las llaves significan "estos elementos literales".
Añadir y eliminar elementos
El sentido de un vector es que crece. push_back añade al final y pop_back elimina del final:
back() devuelve el último elemento y front() el primero: más limpio que v[v.size() - 1] y v[0]. Desde C++11 también puedes usar emplace_back(args...) para construir un elemento en el sitio, lo que evita una copia temporal en tipos más pesados.
Un error habitual en principiantes es llamar a front() o back() sobre un vector vacío. Eso es comportamiento indefinido, no un error: protégete siempre antes con if (!v.empty()).
Leer elementos: [] frente a at()
Indexas un vector exactamente como un array, con []. Pero [] no comprueba los límites: un índice fuera de rango es comportamiento indefinido, que puede leer basura en silencio o fallar más tarde en un lugar confuso:
vector<int> v = {1, 2, 3};
cout << v[10]; // COMPORTAMIENTO INDEFINIDO - sin comprobación, sin error
Cuando quieras seguridad, usa at(). Comprueba el índice y lanza std::out_of_range ante un acceso incorrecto, así obtienes un fallo claro en vez de una corrupción:
Regla práctica: usa [] en bucles ajustados donde ya hayas demostrado que el índice es válido, y at() en los límites donde una entrada incorrecta podría colarse.
Recorrer un vector
La forma más limpia de recorrer un vector es el bucle for basado en rango. Toma los elementos por const auto& para leer sin copiar, o por auto& para editarlos en el sitio:
Si realmente necesitas el índice (para comparar vecinos, por ejemplo), usa un bucle de conteo clásico, pero ten en cuenta que size() devuelve un tipo sin signo (size_t). Comparar un int i con signo contra él puede provocar avisos del compilador y desbordamientos sorprendentes, así que prefiere size_t i o un bucle basado en rango cuando puedas:
for (size_t i = 0; i < v.size(); i++) { // size_t, no int
cout << v[i];
}
size, capacity y reserve
Un vector mantiene dos números: size() (cuántos elementos contiene) y capacity() (cuántos puede contener antes de tener que crecer). Cuando un push_back supera la capacidad, el vector reserva un bloque mayor, copia todos los elementos y libera el bloque antiguo. Por eso un push_back repetido sale barato de forma amortizada, pero cada realocación individual no es gratis:
Si sabes más o menos cuántos elementos vas a añadir, llama primero a reserve() para evitar las realocaciones repetidas. Ten en cuenta que reserve() cambia la capacidad, no el tamaño: el vector sigue teniendo cero elementos hasta que los insertes.
Esta realocación es también el origen del peor error de los vectores. Como crecer mueve el almacenamiento, cualquier puntero, referencia o iterador que hayas guardado dentro del vector queda colgando tras un push_back que realoque:
vector<int> v = {1, 2, 3};
int& first = v[0]; // referencia dentro del vector
v.push_back(4); // puede realocar...
cout << first; // COLGANTE - puede apuntar a memoria liberada
Lo mismo ocurre con los iteradores: no hagas push_back ni erase mientras iteras con un iterador guardado. Si tienes que eliminar elementos mientras recorres, usa el valor de retorno de erase, o el idiom erase-remove con std::remove.
Siguiente: map
Un vector es perfecto cuando buscas cosas por posición: el elemento 0, el elemento 1, etc. Pero a menudo quieres buscar las cosas por una clave: un nombre de usuario, un ID de producto, una palabra. Para eso está std::map. A continuación veremos map, el contenedor clave-valor de C++, incluyendo cómo insertar, buscar e iterar entradas, y la trampa de que [] crea un valor por defecto que confunde a casi todo el mundo.
Preguntas frecuentes
¿Qué es un vector en C++?
Un std::vector es un array dinámico (redimensionable) de la Biblioteca Estándar de C++. A diferencia de un array nativo, conoce su propio tamaño, crece automáticamente cuando añades elementos con push_back y libera su memoria por ti. Incluye <vector> y escribe vector<int> v; para crear uno.
¿Cuál es la diferencia entre [] y at() en un vector de C++?
v[i] no comprueba los límites: un índice fuera de rango es comportamiento indefinido (un fallo o una corrupción silenciosa). v.at(i) comprueba el índice y lanza std::out_of_range si no es válido. Usa [] en bucles intensivos donde ya has validado el índice, y at() cuando quieras un fallo seguro y fácil de depurar.
¿push_back invalida punteros y referencias dentro de un vector de C++?
Sí, potencialmente. Cuando un vector se queda sin capacidad, push_back realoca su almacenamiento en un bloque nuevo, lo que invalida todos los punteros, referencias e iteradores hacia los elementos antiguos. No conserves una referencia a un elemento a través de un push_back, y llama a reserve() por adelantado si puedes para evitar realocaciones inesperadas.